Microsoft & .NETVisual BasicCreating a Gradulated Title Bar

Creating a Gradulated Title Bar

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

When I discovered the cTitleBar class on the vbAccelerator web site, I knew I had to share it with
you.

The class module allows you to paint gradients across your
title bar without losing any of the features (caption, icon and buttons)

Make sure that you have Steve McMahon’s SSubTmr subclassing component
installed and registered on your machine before you try this project. Check vbaccelerator.com for this component.

Add a class module (cTitleBar) and copy the following code
into it:

Option Explicit
' ====================================================
' Filename: cTitlebar.cls
' Author: Steve McMahon
' Date: 25 November 1998
'
' Mostly based on Ben Baird's code, see below.
' Changes:
' -Change Subclass method to SSUBTMR
' -Added WM_STYLECHANGED and WM_SETTEXT to allow
' titlebar to be
' repainted when caption changes.
' -Icon is now drawn transparent rather than 
' with black background
' -Added facility to change start/end and text
' colours (although if
' the end colour isn't the title bar colour, 
' it doesn't really work)
' -Removed WM_SIZE subclass this prevented
' the form from being resized and/or 
' lost the Form_Resize message
' and was also unnecessary
' -Now determines what buttons are present 
' when drawing gradient -Check for tool window 
' and appropriately smaller Tbar
' -Added owner draw background support
'
'
' ====================================================

'//================================================
'+ Gradient titlebar example, version 1.1
'+ Author: Ben Baird
'+ Comments: Uploaded to Visual Basic Thunder
' with Excalibur CodeLib on Monday,
' September 1, 1997.
' Thanks goes to Eric Dimayuga for
' letting me bounce some ideas off
' him. He also assisted me with the
' theory of the code.
'
'+ If you release this code or any 
' modified version of it to the private, 
' I would appreciate some
' credit for the original code.
'+ FIXED IN THIS VERSION:
' - Some trouble with the 
' GradientGetCapsFont routine,
' mainly a problem with the LOGFONT declaration.
' - Added some more cleanup code.
'//================================================

Dim GradhWnd As Long, GradIcon As Long
Dim DrawDC As Long, tmpDC As Long
Dim hRgn As Long
Dim tmpGradFont As Long
Private m_bInSubClass As Boolean
Private m_bMDI As Boolean
Private m_pic As StdPicture
Private m_lHdc As Long
Private m_lHBmpOld As Long
Private m_lhPalOld As Long
Private m_lBitmapW As Long
Private m_lBitmapH As Long
Private m_sFileName As String
Private m_bCustomDraw As Boolean
Private m_bTextTransparent As Boolean
Implements ISubclass
Private m_emr As EMsgResponse
Private m_oColor(1 To 6) As OLE_COLOR
Public Enum EGradTitleBarColors
  eActiveStartColor = 1
  eActiveEndColor = 2
  eActiveText = 3
  einActivestartcolor = 4
  eInactiveEndColor = 5
  eInActiveText = 6
End Enum
Public Enum EGradTitleBarDrawStage
  eDrawBackground
  eDrawIcon
  eDrawText
End Enum

Public Event CustomDraw(ByVal eDrawStage _
  As EGradTitleBarDrawStage, _
  ByRef bDoDefault As Boolean, ByVal lhDC _
  As Long, ByVal lLeft As Long, _
  ByVal lTop As Long, ByVal lWidth As Long, _
  ByVal lHeight As Long, _
  ByVal bActive As Boolean)

Private Type DRAWTEXTPARAMS
  cbSize As Long
  iTabLength As Long
  iLeftMargin As Long
  iRightMargin As Long
  uiLengthDrawn As Long
End Type
Private Type RECT
  Left As Long
  Top As Long
  Right As Long
  Bottom As Long
End Type
Private Type LOGFONT
  lfHeight As Long
  lfWidth As Long
  lfEscapement As Long
  lfOrientation As Long
  lfWeight As Long
  lfItalic As Byte
  lfUnderline As Byte
  lfStrikeOut As Byte
  lfCharSet As Byte
  lfOutPrecision As Byte
  lfClipPrecision As Byte
  lfQuality As Byte
  lfPitchAndFamily As Byte
  lfFaceName As String * 32
End Type

Private Declare Function SystemParametersInfo _
  Lib "user32" Alias _
  "SystemParametersInfoA" _
  (ByVal uAction As Long, ByVal uParam As Long, _
  lpvParam As Any, _
  ByVal fuWinIni As Long) As Long

Private Const SPI_GETNONCLIENTMETRICS = 41

Private Type NONCLIENTMETRICS
 cbSize As Long
  iBorderWidth As Long
  iScrollWidth As Long
  iScrollHeight As Long
  iCaptionWidth As Long
  iCaptionHeight As Long
  lfCaptionFont As LOGFONT
  iSMCaptionWidth As Long
  iSMCaptionHeight As Long
  lfSMCaptionFont As LOGFONT
  iMenuWidth As Long
  iMenuHeight As Long
  lfMenuFont As LOGFONT
  lfStatusFont As LOGFONT
  lfMessageFont As LOGFONT
End Type

Dim CaptionFont As LOGFONT
Private Declare Function CreateFontIndirect _
  Lib "gdi32" Alias _
  "CreateFontIndirectA" _
  (lpLogFont As LOGFONT) As Long

Private Declare Function CallWindowProc Lib _
  "user32" Alias _
  "CallWindowProcA" _
  (ByVal lpPrevWndFunc As Long, ByVal hwnd 
  As Long, ByVal Msg As Long, ByVal _
  wParam As Long, _
  ByVal lParam As Long) As Long

Private Declare Function GetWindow Lib _
  "user32" (ByVal hwnd As Long, _
  ByVal wCmd As Long) As Long

Private Declare Function GetWindowText _
  Lib "user32" _
  Alias "GetWindowTextA" _
  (ByVal hwnd As Long, _
  ByVal lpString As String, _
  ByVal cch As Long) As Long

Private Declare Function GetDC Lib _
  "user32" _
  (ByVal hwnd As Long) As Long

Private Declare Function GetWindowDC _
  Lib "user32" _
  (ByVal hwnd As Long) As Long
Private Declare Function GetWindowRgn _
  Lib "user32" _
  (ByVal hwnd As Long, ByVal hRgn _
  As Long) As Long
Private Declare Function GetWindowRect _
  Lib "user32" _
  (ByVal hwnd As Long, lpRect As RECT) _
  As Long
Private Declare Function GetActiveWindow _
  Lib "user32" () As Long
Private Declare Function GetClassLong _
  Lib "user32" _
  Alias "GetClassLongA" _
  (ByVal hwnd As Long, ByVal nIndex _
  As Long) As Long
Private Declare Function GetWindowLong _
  Lib "user32" _
  Alias "GetWindowLongA" _
  (ByVal hwnd As Long, _
  ByVal nIndex As Long) As Long
Private Declare Function SetClassLong _
  Lib "user32" _
  Alias "SetClassLongA" _
  (ByVal hwnd As Long, _
  ByVal nIndex As Long, ByVal _
  dwNewLong As Long) As Long
Private Declare Function SetWindowLong _
  Lib "user32" _
  Alias "SetWindowLongA" _
  (ByVal hwnd As Long, _
  ByVal nIndex As Long, ByVal _
  dwNewLong As Long) As Long

Private Const GWL_WNDPROC = (-4)
Private Const GWL_STYLE = (-16)
Private Const GWL_EXSTYLE = (-20)
Private Const GCL_WNDPROC = (-24)
Private Const GCL_HICON = (-14)
Private Const WS_BORDER = &H800000
Private Const WS_CAPTION = &HC00000 _
'WS_BORDER Or WS_DLGFRAME
Private Const WS_CHILD = &H40000000
Private Const WS_CHILDWINDOW = (WS_CHILD)
Private Const WS_CLIPCHILDREN = &H2000000
Private Const WS_CLIPSIBLINGS = &H4000000
Private Const WS_DISABLED = &H8000000
Private Const WS_DLGFRAME = &H400000
Private Const WS_EX_ACCEPTFILES = &H10&
Private Const WS_EX_DLGMODALFRAME = &H1&
Private Const WS_EX_NOPARENTNOTIFY = &H4&
Private Const WS_EX_TOPMOST = &H8&
Private Const WS_EX_TRANSPARENT = &H20&
Private Const WS_GROUP = &H20000
Private Const WS_HSCROLL = &H100000
Private Const WS_MINIMIZE = &H20000000
Private Const WS_ICONIC = WS_MINIMIZE
Private Const WS_MAXIMIZE = &H1000000
Private Const WS_MAXIMIZEBOX = &H10000
Private Const WS_MINIMIZEBOX = &H20000
Private Const WS_OVERLAPPED = &H0&
Private Const WS_SYSMENU = &H80000
Private Const WS_THICKFRAME = &H40000
Private Const WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED Or _
  WS_CAPTION Or WS_SYSMENU Or WS_THICKFRAME Or _
  WS_MINIMIZEBOX Or WS_MAXIMIZEBOX)
Private Const WS_POPUP = &H80000000
Private Const WS_POPUPWINDOW = (WS_POPUP Or WS_BORDER _
  Or WS_SYSMENU)
Private Const WS_SIZEBOX = WS_THICKFRAME
Private Const WS_TILED = WS_OVERLAPPED
Private Const WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW
Private Const WS_VISIBLE = &H10000000
Private Const WS_VSCROLL = &H200000
Private Const WS_EX_TOOLWINDOW = &H80&

Private Declare Function GetParent Lib _
  "user32" _
  (ByVal hwnd As Long) As Long

Private Declare Function DeleteDC Lib _
  "gdi32" _
  (ByVal hDC As Long) As Long
Private Declare Function DeleteObject _
  Lib "gdi32" _
  (ByVal hObject As Long) As Long
Private Declare Function OffsetClipRgn _
  Lib "gdi32" _ 
  (ByVal hDC As Long, ByVal X As Long, _
  ByVal Y As Long) As Long
Private Declare Function DestroyIcon Lib _
  "user32" _
  (ByVal hIcon As Long) As Long
Private Declare Function OffsetRect _
  Lib "user32" _
  (lpRect As RECT, ByVal X As Long, _
  ByVal Y As Long) As Long
Private Declare Function DrawIcon _
  Lib "user32" _
  (ByVal hDC As Long, ByVal X _
  As Long, ByVal Y As Long, _
  ByVal hIcon As Long) As Long
Private Declare Function DrawIconEx _
  Lib "user32" _
  (ByVal hDC As Long, ByVal xLeft As Long, _
  ByVal yTop As Long, _
  ByVal hIcon As Long, ByVal cxWidth _
  As Long, ByVal cyWidth As
Long, ByVal istepIfAniCur As Long, _
  hbrFlickerFreeDraw As Long, _
  ByVal diFlags As Long) As Long
Private Const DI_MASK = &H1
Private Const DI_IMAGE = &H2
Private Const DI_NORMAL = &H3
Private Const DI_COMPAT = &H4
Private Const DI_DEFAULTSIZE = &H8

Private Declare Function RectInRegion _
  Lib "gdi32" _
  (ByVal hRgn As Long, lpRect As RECT) As Long
Private Declare Function DrawText _
  Lib "user32" Alias _
  "DrawTextA" (ByVal hDC As Long, _
  ByVal lpStr As String, _
  ByVal nCount As Long, lpRect As RECT, ByVal _
  wFormat As Long) As Long
Private Declare Function DrawTextEx Lib _
  "user32" Alias _
  "DrawTextExA" (ByVal hDC As Long, _
  ByVal lpsz As String, _
  ByVal n As Long, lpRect As RECT, _
  ByVal un As Long, _
  lpDrawTextParams As DRAWTEXTPARAMS) As Long
Private Const DT_SINGLELINE = &H20
Private Const DT_VCENTER = &H4
Private Const DT_END_ELLIPSIS = &H8000&
Private Declare Function ReleaseDC _
  Lib "user32" _
  (ByVal hwnd As Long, ByVal hDC As Long) _
  As Long
Private Declare Function SelectClipRgn _
  Lib "gdi32" _
  (ByVal hDC As Long, ByVal hRgn As Long) _
  As Long
Private Declare Function GetClipRgn Lib _
  "gdi32" _
  (ByVal hDC As Long, ByVal hRgn As Long) _
  As Long
Private Declare Function SendMessage _
  Lib "user32" _
  Alias "SendMessageA" _
  (ByVal hwnd As Long, _
  ByVal wMsg As Long, ByVal wParam As Long, _
  lParam As Any) As Long
Private Declare Function DrawCaption Lib _
  "user32" _
  (ByVal hwnd As Long, ByVal hDC As Long, _
  pcRect As RECT, _
  ByVal un As Long) As Long
Private Declare Function ReleaseCapture _
  Lib "user32" () As Long
Private Declare Function SelectObject _
  Lib "gdi32" _
  (ByVal hDC As Long, ByVal hObject As Long) _
  As Long
Private Declare Function BitBlt Lib _
  "gdi32" (ByVal hDestDC As Long, _
  ByVal X As Long, ByVal Y As Long, _
  ByVal nWidth As Long, _
  ByVal nHeight As Long, ByVal hSrcDC As Long, _
  ByVal xSrc As Long, _
  ByVal ySrc As Long, ByVal _
  dwRop As Long) As Long
Private Const SRCCOPY = &HCC0020
Private Declare Function GetStockObject _
  Lib "gdi32" _
  (ByVal nIndex As Long) As Long
Private Declare Function SetBkMode _
  Lib "gdi32" _
  (ByVal hDC As Long, ByVal nBkMode As Long) _
  As Long
Private Const TRANSPARENT = 1
Private Const OPAQUE = 2
Private Declare Function SetBkColor _
  Lib "gdi32" (ByVal hDC As Long, _
  ByVal crColor As Long) As Long
Private Declare Function OleTranslateColor _
  Lib "OLEPRO32.DLL" _
  (ByVal OLE_COLOR As Long, ByVal HPALETTE As Long, _
  pccolorref As Long) As Long
Private Type BITMAP
  bmType As Long
  bmWidth As Long
  bmHeight As Long
  bmWidthBytes As Long
  bmPlanes As Integer
  bmBitsPixel As Integer
  bmBits As Long
End Type
Private Declare Function GetDesktopWindow _
  Lib "user32" () As Long
Private Declare Function GetObjectAPI Lib _
  "gdi32" Alias _
  "GetObjectA" (ByVal hObject As Long, _
  ByVal nCount As Long, _
  lpObject As Any) As Long

Private Declare Function CopyImage Lib _
  "user32" (ByVal handle As Long, _
  ByVal un1 As Long, ByVal n1 As Long, _
  ByVal n2 As Long, ByVal un2 As Long) _
  As Long
Private Declare Function SetTextColor _
  Lib "gdi32" _
  (ByVal hDC As Long, ByVal crColor As Long) _
  As Long

Private Declare Function GetSystemMetrics _
  Lib "user32" _
  (ByVal nIndex As Long) As Long
Private Const SM_CMETRICS = 44
Private Const SM_CMOUSEBUTTONS = 43
Private Const SM_CXBORDER = 5
Private Const SM_CXCURSOR = 13
Private Const SM_CXDLGFRAME = 7
Private Const SM_CXDOUBLECLK = 36
Private Const SM_CXFIXEDFRAME = SM_CXDLGFRAME
Private Const SM_CXFRAME = 32
Private Const SM_CXFULLSCREEN = 16
Private Const SM_CXHSCROLL = 21
Private Const SM_CXHTHUMB = 10
Private Const SM_CXICON = 11
Private Const SM_CXICONSPACING = 38
Private Const SM_CXMIN = 28
Private Const SM_CXMINTRACK = 34
Private Const SM_CXSCREEN = 0
Private Const SM_CXSMSIZE = 30
Private Const SM_CXSIZEFRAME = SM_CXFRAME
Private Const SM_CXVSCROLL = 2
Private Const SM_CYBORDER = 6
Private Const SM_CYCAPTION = 4
Private Const SM_CYCURSOR = 14
Private Const SM_CYDLGFRAME = 8
Private Const SM_CYDOUBLECLK = 37
Private Const SM_CYFIXEDFRAME = SM_CYDLGFRAME
Private Const SM_CYFRAME = 33
Private Const SM_CYFULLSCREEN = 17
Private Const SM_CYHSCROLL = 3
Private Const SM_CYICON = 12
Private Const SM_CYICONSPACING = 39
Private Const SM_CYKANJIWINDOW = 18
Private Const SM_CYMENU = 15
Private Const SM_CYMIN = 29
Private Const SM_CYMINTRACK = 35
Private Const SM_CYSCREEN = 1
Private Const SM_CYSMSIZE = 31
Private Const SM_CYSIZEFRAME = SM_CYFRAME
Private Const SM_CYVSCROLL = 20
Private Const SM_CYVTHUMB = 9
Private Const SM_DBCSENABLED = 42
Private Const SM_DEBUG = 22
Private Const SM_MENUDROPALIGNMENT = 40
Private Const SM_MOUSEPRESENT = 19
Private Const SM_PENWINDOWS = 41
Private Const SM_RESERVED1 = 24
Private Const SM_RESERVED2 = 25
Private Const SM_RESERVED3 = 26
Private Const SM_RESERVED4 = 27
Private Const SM_SWAPBUTTON = 23
Private Const SM_CYSMCAPTION = 51


Private Declare Function FillRect _
  Lib "user32" _
  (ByVal hDC As Long, lpRect As RECT, _
  ByVal hBrush As Long) As Long
Private Declare Function CreateCompatibleBitmap _
  Lib "gdi32" _
  (ByVal hDC As Long, ByVal nWidth As Long, _
  ByVal nHeight As Long) As Long
Private Declare Function CreateCompatibleDC _
  Lib "gdi32" 
  (ByVal hDC As Long) As Long
Private Declare Function CreateRectRgnIndirect _
  Lib "gdi32" _
  (lpRect As RECT) As Long
Private Declare Function CreateRectRgn _
  Lib "gdi32" (ByVal X1 As Long, _
  ByVal Y1 As Long, ByVal X2 As Long, _
  ByVal Y2 As Long) As Long
Private Declare Function ExcludeClipRect _
  Lib "gdi32" (ByVal hDC As Long, _
  ByVal X1 As Long, ByVal Y1 As Long, _
  ByVal X2 As Long, ByVal Y2 As Long) As Long
Private Declare Function CreateSolidBrush _
  Lib "gdi32" _
  (ByVal crColor As Long) As Long

Private Declare Function DrawFrameControl _
  Lib "user32" (ByVal hDC As Long, _
  lpRect As RECT, ByVal un1 As Long, _
  ByVal un2 As Long) As Long

Private Const DFC_CAPTION = 1
Private Const DFCS_CAPTIONRESTORE = &H3
Private Const DFCS_CAPTIONMIN = &H1
Private Const DFCS_CAPTIONMAX = &H2
Private Const DFCS_CAPTIONHELP = &H4
Private Const DFCS_CAPTIONCLOSE = &H0
Private Const DFCS_INACTIVE = &H100

Private Const WM_ACTIVATE = &H6
Private Const WM_SIZE = &H5
Private Const WM_GETFONT = &H31
Private Const WM_STYLECHANGED = &H7D
Private Const WM_GETTEXT = &HD
Private Const WM_SETTEXT = &HC

' WM_ACTIVATE state values
Private Const WA_INACTIVE = 0
Private Const WA_ACTIVE = 1
Private Const WA_CLICKACTIVE = 2

Private Const WM_NCHITTEST = &H84
Private Const WM_NCPAINT = &H85
Private Const WM_NCACTIVATE = &H86

Private Const WM_MDIACTIVATE = &H222
Private Const WM_MDIGETACTIVE = &H229
Public Property Let TextTransparent_
 (ByVal bState As Boolean)
  m_bTextTransparent = bState
End Property
Public Property Get TextTransparent() _
  As Boolean
  TextTransparent = m_bTextTransparent
End Property
Public Property Let CustomDraw _
 (ByVal bState As Boolean)
  m_bCustomDraw = bState
End Property
Public Property Get CustomDraw() _
  As Boolean
  CustomDraw = m_bCustomDraw
End Property

Public Property Get Color _ 
(ByVal eType As EGradTitleBarColors) _
  As OLE_COLOR
  Color = m_oColor(eType)
End Property
Public Property Let Color _
 (ByVal eType As EGradTitleBarColors, _
  oColor As OLE_COLOR)
  If (m_oColor(eType) <> _
  oColor) Then
    m_oColor(eType) = oColor
    If (GradhWnd <> 0) Then
      SendMessage GradhWnd, _
  WM_NCPAINT, 0, 0
    End If
  End If
End Property

Public Property Get Picture() As StdPicture
  Set Picture = m_pic
End Property
Public Property Let Picture(oPic As StdPicture)
  ' Load a picture from a StdPicture object:
  pClearUp
  If (pbEnsurePicture()) Then
    Set m_pic = oPic
    If (Err.Number = 0) Then
      pbGetBitmapIntoDC
    End If
  End If
End Property
Private Function pbEnsurePicture() As Boolean
  On Error Resume Next
  pbEnsurePicture = True
  If (m_pic Is Nothing) Then
    Set m_pic = New StdPicture
    If (Err.Number <> 0) Then
      pErr 3, "Unable to allocate _
      memory for picture object."
      pbEnsurePicture = False
    End If
  End If
  On Error GoTo 0
  Exit Function
End Function
Private Function pbGetBitmapIntoDC() As Boolean
  Dim tB As BITMAP
  Dim lhDC As Long, lHwnd As Long

  ' Make a DC to hold the picture bitmap 
  ' which we can blt from:
  lHwnd = GetDesktopWindow()
  lhDC = GetDC(lHwnd)
  m_lHdc = CreateCompatibleDC(lhDC)
  ReleaseDC lHwnd, lhDC
  If (m_lHdc <> 0) Then
    ' Get size of bitmap:
    GetObjectAPI m_pic.handle, LenB(tB), tB
    m_lBitmapW = tB.bmWidth
    m_lBitmapH = tB.bmHeight

    ' Select bitmap into DC:
    m_lHBmpOld = SelectObject(m_lHdc, m_pic.handle)
    If (m_lHBmpOld <> 0) Then
      ' Select the palette
      into the DC:
      m_lhPalOld =
      SelectObject(m_lHdc, m_pic.hPal)
      pbGetBitmapIntoDC =
      True
    Else
      pClearUp
      pErr 2, "Unable to
      select bitmap into DC"
    End If
  Else
    pErr 1, "Unable to create compatible
    DC"
  End If
End Function

Private Sub pClearUp()
  ' Clear reference to the filename:
  m_sFileName = ""
  ' If we have a DC, then clear up:
  If (m_lHdc <> 0) Then
    ' Select the bitmap out of DC:
    If (m_lHBmpOld <> 0) Then
      SelectObject m_lHdc, m_lHBmpOld
      ' The original bitmap does not have to 
      ' deleted because it is owned by m_pic
    End If
    ' Select the palette out of the DC:
    If (m_lhPalOld <> 0) Then
      SelectObject m_lHdc, m_lhPalOld
      ' The original palette does not have _
      ' to deleted because it is owned by m_pic
    End If
    ' Remove the DC:
    DeleteObject m_lHdc
  End If
End Sub

Private Sub pErr(lNumber As Long, sMsg As String)

  Err.Raise vbObjectError + 1048 + lNumber, _
    App.EXEName & _
    ".cTitleBar", sMsg & ", _
    [" & lNumber & _
    "]"
End Sub

Private Function GradientCallback _
  (ByVal hwnd As Long, _
  ByVal wMsg As Long, ByVal wParam _
  As Long, ByVal lParam _
  As Long) As Long

  Dim OldBMP As Long, NewBMP As Long
  Dim rcWnd As RECT

  Select Case wMsg
    Case WM_NCACTIVATE, WM_MDIACTIVATE

      'Debug.Print "ACTIVATE"

      GetWindowRect GradhWnd, rcWnd

      'Create memory DC to draw the titlebar in.
      tmpDC = GetWindowDC(GradhWnd)
      DrawDC = CreateCompatibleDC(tmpDC)
      NewBMP = CreateCompatibleBitmap _
       (tmpDC, rcWnd.Right - rcWnd.Left, 50)
      OldBMP = SelectObject(DrawDC, NewBMP)
      With rcWnd
        hRgn = CreateRectRgn(.Left, .Top, .Right, .Bottom)
        SelectClipRgn tmpDC, hRgn
        OffsetClipRgn tmpDC, -.Left, -.Top
      End With
      'Find out what color the titlebar needs
      'to be...
      If wParam And GetParent(GradhWnd) = 0 Then

        DrawGradient TranslateColor _
            (Color(eActiveStartColor)), _
            TranslateColor(Color(eActiveEndColor)), True

      ElseIf wParam = GradhWnd And GetParent _
            (GradhWnd) <> 0 Then

        DrawGradient TranslateColor _
          (Color(einActivestartcolor)),_
          TranslateColor(Color(eInactiveEndColor)), _
          False

      ElseIf SendMessage(GetParent(GradhWnd), _
          WM_MDIGETACTIVE, 0, 0) = GradhWnd Then

        DrawGradient TranslateColor(Color(eActiveStartColor)), _
           TranslateColor(Color(eActiveEndColor)), True

      Else

        DrawGradient TranslateColor(Color(einActivestartcolor)), _
            TranslateColor(Color(eInactiveEndColor)), False

      End If

      'Cleanup
      SelectObject DrawDC, OldBMP
      DeleteObject NewBMP
      DeleteDC DrawDC
      OffsetClipRgn tmpDC, rcWnd.Left, rcWnd.Top
      GetClipRgn tmpDC, hRgn
      ReleaseDC GradhWnd, tmpDC
      DeleteObject hRgn
      tmpDC = 0
      GradientCallback = 1
      Exit Function

    Case WM_NCPAINT

      'Debug.Print "NCPAINT"

      'Basically same as above.
      GetWindowRect GradhWnd, rcWnd
      tmpDC = GetWindowDC(GradhWnd)
      DrawDC = CreateCompatibleDC(tmpDC)
      NewBMP = CreateCompatibleBitmap _
        (tmpDC, rcWnd.Right - rcWnd.Left, 50)
      OldBMP = SelectObject(DrawDC, NewBMP)
      With rcWnd
        hRgn = CreateRectRgn _
          (.Left, .Top, .Right, .Bottom)
        SelectClipRgn tmpDC, hRgn
        OffsetClipRgn tmpDC, -.Left, -.Top
      End With
      'Get the color to paint the caption with.
      If GetActiveWindow() = GradhWnd Then
        DrawGradient TranslateColor _
          (Color(eActiveStartColor)), _
          TranslateColor(Color(eActiveEndColor)), True
      ElseIf SendMessage(GetParent(GradhWnd), _
        WM_MDIGETACTIVE, 0, 0) = GradhWnd Then
        DrawGradient TranslateColor(Color(eActiveStartColor)), _
          TranslateColor(Color(eActiveEndColor)), True
      Else
        DrawGradient TranslateColor _
          (Color(einActivestartcolor)), _
          TranslateColor(Color(eInactiveEndColor)), False
      End If

      'Cleanup
      SelectObject DrawDC, OldBMP
      DeleteObject NewBMP
      DeleteDC DrawDC
      OffsetClipRgn tmpDC, rcWnd.Left, rcWnd.Top
      GetClipRgn tmpDC, hRgn

      'Call the old proc. This will only
      'draw the titlebar's min/max/close buttons
      'because we told it not to do the rest (this
      'eliminates flicker.)
      GradientCallback = CallOldWindowProc(hwnd, _
        WM_NCPAINT, hRgn, lParam)
      ReleaseDC GradhWnd, tmpDC
      DeleteObject hRgn
      tmpDC = 0
      Exit Function

    Case WM_SIZE, WM_STYLECHANGED, WM_SETTEXT
      'Whoa, we need to paint the caption.
      If hwnd = GradhWnd Then SendMessage GradhWnd, _
        WM_NCPAINT, 0, 0
  End Select

End Function

Public Sub RefreshTitleBar()
  If GradhWnd <> 0 Then SendMessage GradhWnd, _
  WM_NCPAINT, 0, 0
End Sub

Public Sub GradientForm(frm As Object)

  If (m_bInSubClass) Then
    GradientReleaseForm
  End If

  GradhWnd = frm.hwnd
  GradIcon = frm.Icon

  If (TypeOf frm Is MDIForm) Then
    m_bMDI = True
  End If

  AttachMessage Me, GradhWnd, WM_NCPAINT
  AttachMessage Me, GradhWnd, WM_NCACTIVATE
  AttachMessage Me, GradhWnd, WM_MDIACTIVATE
  ' Check for change in form caption:
  AttachMessage Me, GradhWnd, WM_STYLECHANGED
  AttachMessage Me, GradhWnd, WM_SETTEXT

  m_bInSubClass = True

  GradientGetCapsFont

End Sub

Public Sub GradientReleaseForm()

 If (m_bInSubClass) Then
   DetachMessage Me, GradhWnd, WM_NCPAINT
   DetachMessage Me, GradhWnd, WM_NCACTIVATE
   DetachMessage Me, GradhWnd, WM_MDIACTIVATE
   DetachMessage Me, GradhWnd, WM_STYLECHANGED
   DetachMessage Me, GradhWnd, WM_SETTEXT
   GradhWnd = 0
 End If

End Sub

Private Function DrawGradient( _
  ByVal Color1 As Long, _
  ByVal Color2 As Long, _
  ByVal bActive As Boolean _
  ) As Long

  Dim i As Integer
  Dim DestWidth As Long, DestHeight As Long
  Dim StartPnt As Integer, EndPnt As Integer
  Dim PixelStep As Long, XBorder As Long
  Dim WndRect As RECT
  Dim OldFont As Long
  Dim fText As String
  Dim iBmTop As Long
  Dim lS As Long
  Dim lBtnWidth As Long
  Dim bDoDefault As Boolean
  Dim bShowBitmap As Boolean

  On Error Resume Next

  GetWindowRect GradhWnd, WndRect
  With WndRect
  DestWidth = .Right - .Left
  End With

  bShowBitmap = ((m_lHdc <> 0) And _
   (DestWidth > (m_lBitmapW * 4)))

  'Get height of caption bar
  lS = GetWindowLong(GradhWnd, GWL_EXSTYLE)_
  If ((lS And WS_EX_TOOLWINDOW) = 
   WS_EX_TOOLWINDOW) Then
    DestHeight = GetSystemMetrics(SM_CYSMCAPTION)
  Else
    DestHeight = GetSystemMetrics(SM_CYCAPTION)
  End If

  'Get the text of the form's caption
  fText = Space$(255)
  GetWindowText GradhWnd, fText, 255
  fText = Trim$(fText)

  'Get the width of the border
  XBorder = GetSystemMetrics(SM_CXFRAME)

  'The width of the area we need to paint:
  DestWidth = DestWidth - (XBorder * 2) + 6
  lS = GetWindowLong(GradhWnd, GWL_STYLE)
  If ((lS And WS_MINIMIZEBOX) = WS_MINIMIZEBOX) _
    Or ((lS And WS_MAXIMIZEBOX) = WS_MAXIMIZEBOX) Then
    lBtnWidth = lBtnWidth + (GetSystemMetrics _
    (SM_CXSMSIZE)) * 2
  End If
  If ((lS And WS_SYSMENU) = WS_SYSMENU) Then
    lBtnWidth = lBtnWidth + (GetSystemMetrics(SM_CXSMSIZE))
  End If
  If (lBtnWidth <> 0) Then lBtnWidth = lBtnWidth + 1
  DestWidth = DestWidth - lBtnWidth

  'Where the painting begins:
  StartPnt = XBorder
  'Where the painting ends:
  EndPnt = XBorder + DestWidth - 4

  'How many steps do we need to
  'paint the titlebar?
  If (bShowBitmap) Then
    PixelStep = DestWidth - 16 - m_lBitmapW
  Else
    PixelStep = DestWidth  8
  End If

  ReDim Colors(PixelStep) As Long
  'Create gradient colors in the array
  GradateColors Colors(), Color1, Color2

  Dim rct As RECT
  Dim hBr As Long

  With rct
    .Top = XBorder
    .Left = XBorder
    .Right = XBorder + (DestWidth  PixelStep)
    .Bottom = XBorder + DestHeight - 1
    bDoDefault = True
    If (m_bCustomDraw) Then
      RaiseEvent CustomDraw(eDrawBackground, _
            bDoDefault, DrawDC, _
            XBorder, XBorder, EndPnt - XBorder, _
            DestHeight - 1, bActive)
    End If

    If (bDoDefault) Then

      If (bShowBitmap) Then
        ' the first box is going to extend 
        ' all the way from
        ' the icon to the end of the bitmap, 
        ' and the gradient
        ' is only doing to start afterwards:
        rct.Right = rct.Left + 16 + m_lBitmapW
      End If
      hBr = CreateSolidBrush(Colors(0))
      FillRect DrawDC, rct, hBr
      DeleteObject hBr
      If (bShowBitmap) Then
        rct.Left = rct.Right
        rct.Right = rct.Left + (DestWidth  PixelStep)
      End If
      For i = 1 To PixelStep - 1
        'Paint the titlebar in increments, increasing
        'the color index with each iteration.
        hBr = CreateSolidBrush(Colors(i))
        FillRect DrawDC, rct, hBr
        'Cleanup
        DeleteObject hBr
        'Prepare for the next iteration
        OffsetRect rct, (DestWidth  PixelStep), 0
        If i = PixelStep - 2 Then .Right = EndPnt
      Next

    End If

    bDoDefault = True
    If (m_bCustomDraw) Then
      RaiseEvent CustomDraw(eDrawIcon, bDoDefault, DrawDC, _
        XBorder, XBorder, EndPnt - XBorder, DestHeight - 1, _
        bActive)
    End If
    If (bDoDefault) Then
      If GradIcon <> 0 Then 'Paint the icon

        'Move the caption text's start point over

        'to make room for the icon

        .Left = XBorder + GetSystemMetrics(SM_CXSMSIZE) + 2

        DrawIconEx DrawDC, XBorder + 1, XBorder + 1, GradIcon, _
          GetSystemMetrics(SM_CXSMSIZE) - 2, _
          GetSystemMetrics(SM_CYSMSIZE) _
          - 2, ByVal 0&, ByVal 0&, DI_NORMAL

      Else

        'No icon

        .Left = XBorder

      End If
    End If

    'If we have a picture:
    If (bShowBitmap) Then
      ' Draw it and shift left & right
      iBmTop = 2 + .Top + ((.Bottom - .Top - m_lBitmapH)  2)
      BitBlt DrawDC, .Left, iBmTop, m_lBitmapW, m_lBitmapH, _
        m_lHdc, 0, 0, SRCCOPY
      .Left = .Left + m_lBitmapW + 2
    Else
      .Left = .Left + 2
    End If

    bDoDefault = True
    If (m_bCustomDraw) Then
      RaiseEvent CustomDraw(eDrawText, bDoDefault, DrawDC, XBorder, _
        XBorder, EndPnt - XBorder - 10, DestHeight - 1, bActive)
    End If
    If (bDoDefault) Then

      'If getting the caption font failed, use the font
      'from the gradient caption form.
      If CaptionFont.lfHeight = 0 And tmpGradFont = 0 Then
        tmpGradFont = SendMessage(GradhWnd, WM_GETFONT, 0, 0)
      ElseIf tmpGradFont = 0 Then
        tmpGradFont = CreateFontIndirect(CaptionFont)
      End If
      OldFont = SelectObject(DrawDC, tmpGradFont)
      'This is like setting FontTransparent on a Form to True:
      SetBkMode DrawDC, 1
      'Use a white caption, since the background is black
      'on the left side
      If (bActive) Then

        SetTextColor DrawDC, TranslateColor(m_oColor(eActiveText))
        '&HFFFFFF
        'RGB(255, 255, 255)
      Else

        SetTextColor DrawDC, TranslateColor(m_oColor(eInActiveText)) 
        '&HC0C0C0
        'RGB(128, 128, 128)
      End If
      .Right = .Right - 10
      'Draw the caption text
      If (m_bTextTransparent) Then
        SetBkMode DrawDC, TRANSPARENT
      Else
        SetBkMode DrawDC, OPAQUE
        If (bActive) Then
          SetBkColor DrawDC, TranslateColor _
          (m_oColor(eActiveStartColor))
        Else
          SetBkColor DrawDC, TranslateColor _
          (m_oColor(einActivestartcolor))
        End If
      End If
      DrawText DrawDC, fText, Len(fText) - 1, _
          rct, DT_SINGLELINE Or _ DT_END_ELLIPSIS _
          Or DT_VCENTER
      'Cleanup
      SelectObject DrawDC, OldFont
      DeleteObject tmpGradFont
      tmpGradFont = 0
    End If

    .Left = XBorder
    .Right = .Right + 12
    If tmpDC <> 0 Then
      'Blit our work from the memory DC to the form's
      'window DC to finish the job.
      BitBlt tmpDC, .Left, .Top, .Right - .Left - 10, _
        .Bottom - .Top, _ DrawDC, .Left, .Top, vbSrcCopy
      'Tell windows that we already painted most of
      'the titlebar.
      ExcludeClipRect tmpDC, XBorder, XBorder, .Right - _
        .Left - 7, .Bottom - .Top + 4

    End If

  End With

End Function

Private Sub GradateColors(Colors() As Long, _
  ByVal Color1 As Long, ByVal Color2 As Long)

  'Alright, I admit -- this routine was
  'taken from a VBPJ issue a few months back.

  Dim i As Integer
  Dim dblR As Double, dblG As Double, dblB As Double
  Dim addR As Double, addG As Double, addB As Double
  Dim bckR As Double, bckG As Double, bckB As Double

  dblR = CDbl(Color1 And &HFF)
  dblG = CDbl(Color1 And &HFF00&) / 255
  dblB = CDbl(Color1 And &HFF0000) / &HFF00&
  bckR = CDbl(Color2 And &HFF&)
  bckG = CDbl(Color2 And &HFF00&) / 255
  bckB = CDbl(Color2 And &HFF0000) / &HFF00&

  addR = (bckR - dblR) / UBound(Colors)
  addG = (bckG - dblG) / UBound(Colors)
  addB = (bckB - dblB) / UBound(Colors)

  For i = 0 To UBound(Colors)
  dblR = dblR + addR
  dblG = dblG + addG
  dblB = dblB + addB
  If dblR > 255 Then dblR = 255
  If dblG > 255 Then dblG = 255
  If dblB > 255 Then dblB = 255
  If dblR < 0 Then dblR = 0
  If dblG < 0 Then dblG = 0
  If dblG < 0 Then dblB = 0
  Colors(i) = RGB(dblR, dblG, dblB)
  Next

End Sub

Private Sub GradientGetCapsFont()

  'Tries to retrieve the Windows caption font
  'in the current Appearance scheme. Doesn't
  'seem to work all the time, so if anyone knows
  'why I'd appreciate being told.

  Dim NCM As NONCLIENTMETRICS
  Dim lfNew As LOGFONT

  NCM.cbSize = Len(NCM)
  Call SystemParametersInfo(SPI_GETNONCLIENTMETRICS, _
    0, NCM, 0)

  If NCM.iCaptionHeight = 0 Then
    CaptionFont.lfHeight = 0
  Else
    CaptionFont = NCM.lfCaptionFont
  End If

End Sub
Private Function TranslateColor(ByVal clr As OLE_COLOR, _
  Optional hPal As Long = 0) As Long
 
  If OleTranslateColor(clr, hPal, TranslateColor) Then
    TranslateColor = -1 'CLR_INVALID
  End If

End Function

Private Sub Class_Initialize()
  m_bCustomDraw = True
  m_bTextTransparent = True
  m_oColor(eActiveStartColor) = 0
  m_oColor(einActivestartcolor) = 0
  m_oColor(eActiveEndColor) = vbActiveTitleBar
  m_oColor(eInactiveEndColor) = vbInactiveTitleBar
  m_oColor(eActiveText) = &HFFFFFF
  m_oColor(eInActiveText) = &HC0C0C0
End Sub

Private Sub Class_Terminate()
  GradientReleaseForm
End Sub

Private Property Let ISubclass_MsgResponse _
 (ByVal RHS As SSubTimer.EMsgResponse)
  m_emr = RHS
End Property

Private Property Get ISubclass_MsgResponse() _
  As SSubTimer.EMsgResponse
  'Debug.Print "Get MsgResponse", _
   CurrentMessage
  Select Case CurrentMessage
    Case WM_NCACTIVATE, WM_MDIACTIVATE
      'This will cause a slight flicker because we
      'let Windows paint the caption before we do.
      'We don't let this happen in the WM_NCPAINT message,
      'which is called more often than NCACTIVATE.
      m_emr = emrPreprocess

    Case WM_NCPAINT
      'Call the old proc. This will only
      'draw the titlebar's min/max/close buttons
      'because we told it not to do the rest (this
      'eliminates flicker.
      m_emr = emrConsume

    Case Else
      m_emr = emrPostProcess
  End Select

  ISubclass_MsgResponse = m_emr
End Property

Private Function ISubclass_WindowProc(ByVal hwnd As Long, _
  ByVal iMsg As Long, ByVal wParam As Long, _
  ByVal lParam As Long) As Long
 
  'Debug.Print "Get Msg" & vbCrLf
  ISubclass_WindowProc = GradientCallback _
  (hwnd, iMsg, wParam, lParam)
End Function

Add a new form (frmTest) and copy this code into the
General Declarations procedure:

'This project was provided by VB Square in the form
'of a How-To on the web site.
'Please recognise the author's in the Class module
'For more, detailed How-To's, code and other VB stuff
'visit VB Square - www.programmerz.com/vb/
Private m_ct As New cTitleBar

Private Sub Form_Load()
  m_ct.GradientForm (Me)
End Sub

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories