Visual Basic Subclassing Routines
WM_PAINT: Subclassing to Create a Flat Combo Box
     
Posted:   Wednesday November 24, 1999
Updated:   Monday December 26, 2011
     
Applies to:   VB5, VB6
Developed with:   VB6, Windows 98
OS restrictions:   None
Author:   Klaus Probst, VBnet - Randy Birch
     
 Prerequisites
VB5 or VB6.

Office 97 made popular the Flat combo box, and while it takes some work to get a VB combo to react as the Office 97 one does, it is a straight forward exercise to simply adjust the combo box edge styles via subclassing to provide the appearance of a flat or stylized combo.

Unlike other controls, the combo redraws its frame elements whenever it receives a paint message. This anomaly removes the possibility of using straight-forward style-bit change to affect a change in the control's appearance. So, by subclassing the combo and responding to the WM_PAINT message sent it, we can introduce our own modifications to the edge styles, which includes eliminating them altogether.

This demo then shows how to subclass and capture the WM_PAINT message for the combo, and in doing so display the combo in three different styles - normal, borderless (aka flat), and with a softer combo edge (my favourite). The illustration at the top shows the effect in Windows 2000 and earlier, while the bottom is for Windows XP using a manifest for to achieve the XP styles on a VB form. On both illustrations, the top combo shows how the the normal VB appears by default, while the middle combo is this same "normal VB combo" but with the subclassing applied in flat mode. The final combo on each form has subclassing, but with the soft edge style applied. Note those with the the drop shadow are taken from second screen captures of Combo2, so for this demo you only need to add two combos to the form and the code will toggle the look of the second combo.

The code for the flat combo is by Klaus Probst as provided in the msnews WinAPI newsgroup.  The subclassing routine uses Karl Peterson's HookMe routine, and is used with permission.

 BAS Module - 'HookMe.bas'
Place the following code into the general declarations area of a bas module:

Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright 1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' *************************************************************************
'  Copyright (C)1997 Karl E. Peterson and Zane Thomas, All Rights Reserved
'
'  Used at VBnet by permission.
'  For the latest version see the Tools section at http://www.mvps.org/vb/
' *************************************************************************
'  Warning: This computer program is protected by copyright law and
'  international treaties. Unauthorized reproduction or distribution
'  of this program, or any portion of it, may result in severe civil
'  and criminal penalties, and will be prosecuted to the maximum
'  extent possible under the law.
' *************************************************************************

Public Declare Function GetProp Lib "user32" _
   Alias "GetPropA" _
  (ByVal hwnd As Long, ByVal lpString As String) As Long

Public 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 SetProp Lib "user32" _
   Alias "SetPropA" _
  (ByVal hwnd As Long, ByVal lpString As String, _
   ByVal hData As Long) As Long

Private Declare Function SetWindowLong Lib "user32" _
   Alias "SetWindowLongA" _
  (ByVal hwnd As Long, ByVal nIndex As Long, _
   ByVal wNewWord As Long) As Long

Private Declare Function GetWindowLong Lib "user32" _
   Alias "GetWindowLongA" _
  (ByVal hwnd As Long, ByVal nIndex As Long) As Long

Private Declare Sub CopyMemory Lib "kernel32" _
   Alias "RtlMoveMemory" _
  (Destination As Any, Source As Any, ByVal Length As Long)

Private Const GWL_WNDPROC  As Long = (-4)


Public Function HookFunc(ByVal hwnd As Long, _
                         ByVal msg As Long, _
                         ByVal wp As Long, _
                         ByVal lp As Long) As Long
   Dim foo As Long
   Dim obj As Form1

   foo = GetProp(hwnd, "ObjectPointer")
   '
   ' Ignore "impossible" bogus case
   '
   If (foo <> 0) Then
      CopyMemory obj, foo, 4
      On Error Resume Next
      HookFunc = obj.WindowProc(hwnd, msg, wp, lp)
      If (Err) Then
         UnhookWindow hwnd
         Debug.Print "Unhook on Error, #"; CStr(Err.Number)
         Debug.Print "  Desc: "; Err.Description
         Debug.Print "  Message, hWnd: &h"; Hex(hwnd), "Msg: &h"; Hex(msg), "Params:"; wp; lp
      End If
      '
      ' Make sure we don't get any foo->Release() calls
      '
      foo = 0
      CopyMemory obj, foo, 4
   End If
End Function


Public Sub HookWindow(hwnd As Long, thing As Object)

   Dim foo As Long

   CopyMemory foo, thing, 4

   Call SetProp(hwnd, "ObjectPointer", foo)
   Call SetProp(hwnd, "OldWindowProc", GetWindowLong(hwnd, GWL_WNDPROC))
   Call SetWindowLong(hwnd, GWL_WNDPROC, AddressOf HookFunc)

End Sub


Public Sub UnhookWindow(hwnd As Long)
   Dim foo As Long

   foo = GetProp(hwnd, "OldWindowProc")
   If (foo <> 0) Then
      Call SetWindowLong(hwnd, GWL_WNDPROC, foo)
   End If
End Sub


Public Function InvokeWindowProc(hwnd As Long, _
                                 msg As Long, _
                                 wp As Long, _
                                 lp As Long) As Long
                                 
   InvokeWindowProc = CallWindowProc(GetProp(hwnd, "OldWindowProc"), _
                                     hwnd, msg, wp, lp)
   
End Function
 Form Code
Add a form to the project and leave the name as Form1. If you chose another name, remember to change the HookFunc declare from "Dim Form1 as Object" to the correct name of your form!!

Add two combos to the form (Combo1 and Combo2), as well as two command buttons (Command1 and Command2). The third combo in the illustration is a replication of the subclassed combo showing the second style appearance.  Labels are optional. Add the following code to the form:


Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright 1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Klaus H. Probst - http://www.vbbox.com/

Private cStyle As Boolean

'DrawEdge() constants
Private Const BDR_RAISEDOUTER = &H1
Private Const BDR_SUNKENOUTER = &H2
Private Const BDR_RAISEDINNER = &H4
Private Const BDR_SUNKENINNER = &H8
Private Const BDR_OUTER = &H3
Private Const BDR_INNER = &HC
Private Const BDR_RAISED = &H5
Private Const BDR_SUNKEN = &HA

Private Const EDGE_RAISED = (BDR_RAISEDOUTER Or BDR_RAISEDINNER)
Private Const EDGE_INNER = (BDR_SUNKENINNER Or BDR_RAISEDINNER)
Private Const EDGE_SUNKEN = (BDR_SUNKENOUTER Or BDR_SUNKENINNER)
Private Const EDGE_ETCHED = (BDR_SUNKENOUTER Or BDR_RAISEDINNER)
Private Const EDGE_BUMP = (BDR_RAISEDOUTER Or BDR_SUNKENINNER)

Private Const BF_LEFT = &H1
Private Const BF_TOP = &H2
Private Const BF_RIGHT = &H4
Private Const BF_BOTTOM = &H8

Private Const BF_TOPLEFT = (BF_TOP Or BF_LEFT)
Private Const BF_TOPRIGHT = (BF_TOP Or BF_RIGHT)
Private Const BF_BOTTOMLEFT = (BF_BOTTOM Or BF_LEFT)
Private Const BF_BOTTOMRIGHT = (BF_BOTTOM Or BF_RIGHT)
Private Const BF_RECT = (BF_LEFT Or BF_TOP Or BF_RIGHT Or BF_BOTTOM)
Private Const BF_DIAGONAL = &H10

Private Const BF_DIAGONAL_ENDTOPRIGHT = (BF_DIAGONAL Or BF_TOP Or BF_RIGHT)
Private Const BF_DIAGONAL_ENDTOPLEFT = (BF_DIAGONAL Or BF_TOP Or BF_LEFT)
Private Const BF_DIAGONAL_ENDBOTTOMLEFT = (BF_DIAGONAL Or BF_BOTTOM Or BF_LEFT)
Private Const BF_DIAGONAL_ENDBOTTOMRIGHT = (BF_DIAGONAL Or BF_BOTTOM Or BF_RIGHT)

Private Const BF_MIDDLE = &H800    'Fill in the middle.
Private Const BF_SOFT = &H1000     'Use for softer buttons.
Private Const BF_ADJUST = &H2000   'Calculate the space left over.
Private Const BF_FLAT = &H4000     'For flat rather than 3-D borders.
Private Const BF_MONO = &H8000&    'For monochrome borders

Private Const WM_PAINT = &HF

Private Type RECT
   Left As Long
   Top As Long
   Right As Long
   Bottom As Long
End Type

Private Declare Function DrawEdge Lib "user32" _
  (ByVal hDC As Long, _
   qrc As RECT, _
   ByVal edge As Long, _
   ByVal grfFlags As Long) As Long

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

Private Declare Function ReleaseDC Lib "user32" _
  (ByVal hwnd As Long, _
   ByVal hDC As Long) As Long

Private Declare Function GetClientRect Lib "user32" _
  (ByVal hwnd As Long, _
   lpRect As RECT) As Long

Private Declare Function InflateRect Lib "user32" _
  (lpRect As RECT, _
   ByVal X As Long, _
   ByVal Y As Long) As Long




Private Sub Command1_Click()

  'this alters the subclassed state (for comparing)
   Static cBorder As Boolean
   
   cBorder = Not cBorder
   Command1.Caption = IIf(cBorder, "Subclassed", "Normal")
   Command2.Enabled = cBorder = True
   
   If cBorder Then
      Call HookWindow(Combo2.hwnd, Me)
   Else
      Call UnhookWindow(Combo2.hwnd)
   End If
   
   Combo2.Refresh
   
End Sub


Private Sub Command2_Click()

  'this just demos two possible style
  'combinations used in the WindowProc
  'WM_PAINT message
   cStyle = Not cStyle
   
   Command2.Caption = IIf(cStyle, "Soft", "Flat")
   
   Combo2.Refresh
   
End Sub


Private Sub Form_Load()

   Dim cnt As Long
   
  'add a few fonts to both combos
   For cnt = 1 To Screen.FontCount \ 2
       Combo2.AddItem Screen.Fonts(cnt)
       Combo1.AddItem Screen.Fonts(cnt)
   Next cnt
   
  'set options and subclass
   Command2_Click
   Command1_Click

End Sub


Private Sub Form_Unload(Cancel As Integer)

   Call UnhookWindow(Combo2.hwnd)

End Sub


Friend Function WindowProc(hwnd As Long, _
                           msg As Long, _
                           wp As Long, _
                           lp As Long) As Long
   
   Dim hDcCombo As Long
   Dim rc As RECT

   Select Case hwnd
      Case Combo2.hwnd
         
         Select Case msg
         
            Case WM_PAINT
            
               WindowProc = CallWindowProc(GetProp(hwnd, _
                                           "OldWindowProc"), _
                                           hwnd, msg, _
                                           wp, lp)
               
               hDcCombo = GetWindowDC(hwnd)
               Call GetClientRect(hwnd, rc)
                 
               If cStyle Then
                 
                  Call DrawEdge(hDcCombo, rc, _
                                BDR_RAISEDINNER, _
                                BF_RECT Or _
                                BF_FLAT Or _
                                BF_ADJUST)
                                
                  Call InflateRect(rc, -1, -1)
                  
                  Call DrawEdge(hDcCombo, rc, _
                                BDR_RAISEDINNER, _
                                BF_RECT Or _
                                BF_FLAT Or _
                                BF_ADJUST)
                                
                  Call ReleaseDC(hwnd, hDcCombo)
               
               Else
               
                  Call DrawEdge(hDcCombo, rc, _
                                BDR_RAISEDINNER, _
                                BF_RECT Or _
                                BF_FLAT)
                                
                  Call InflateRect(rc, -1, -1)
                  
                  Call DrawEdge(hDcCombo, rc, _
                                BDR_RAISEDINNER, _
                                BF_RECT Or _
                                BF_FLAT)
                  
                  Call ReleaseDC(hwnd, hDcCombo)
                 
               End If
         
             Case Else
             
                'Pass to default window procedure
                 WindowProc = CallWindowProc(GetProp(hwnd, _
                                             "OldWindowProc"), _
                                             hwnd, msg, _
                                             wp, lp)
                 
         End Select

      Case Else
        
        'Pass to default window procedure
         WindowProc = CallWindowProc(GetProp(hwnd, _
                                     "OldWindowProc"), _
                                     hwnd, msg, _
                                     wp, lp)
   End Select

End Function
 Comments
Save then run the project. Combo2 will appear with the soft style. Pressing the 'Soft' button will toggle the style to Flat and back. Command1 turns the subclassing off.

 
 

PayPal Link
Make payments with PayPal - it's fast, free and secure!

 
 
 
 

Copyright 1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy

 

Hit Counter