Visual Basic Subclassing Routines
WM_LBUTTONDOWN: Substitute a ListView for a Combo's Dropdown List
Posted:   Friday March 16, 2001
Updated:   Monday December 26, 2011
Applies to:   VB5, VB6
Developed with:   VB6, Windows 2000
OS restrictions:   None
Author:   VBnet - Randy Birch


VBnet CoolTabs
GetTextExtentPoint32: Right-Align List Box Data
SetWindowLong: Right-Align List Box Data and/or the Scrollbar
SetWindowLong: Right-Align List Contents in a Combo
SendMessage: Align Text Box Contents Using Tabstops

SendMessage: Align List Box Contents Using Tabstops
WM_LBUTTONDOWN: Substitute a Tabbed List for a Combo's Dropdown List
WM_LBUTTONDOWN: Substitute a ListView for a Combo's Dropdown List
VB5 or VB6.

Once the methods to trap the display of a combo's default dropdown list had been coded, substituting another control in place of the dropdown list is as easy as modifying the basic routine to point to the new control. Based on the code listed above, the only changes needed to support a listview control instead of a list box centred around setting the ListView's initial properties and data, changing all "List1" references to "ListView1", and modifying the code to  extract the selected item to handle the specifics of a ListItem.

For a full description of the methods, and the code description, please see the related link above.

 BAS Module Code
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.
Public defWinProc As Long

Public Const GWL_WNDPROC As Long = -4
Private Const CBN_DROPDOWN As Long = 7
Private Const WM_LBUTTONDOWN As Long = &H201
Private Const WM_KEYDOWN As Long = &H100
Private Const VK_F4 As Long = &H73

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 SendMessage Lib "user32" _
   Alias "SendMessageA" _
  (ByVal hwnd As Long, _
   ByVal wMsg As Long, _
   ByVal wParam As Long, _
   lParam As Any) As Long

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

Public Sub Unhook(hwnd As Long)
   If defWinProc <> 0 Then
      Call SetWindowLong(hwnd, _
                         GWL_WNDPROC, _
      defWinProc = 0
   End If
End Sub

Public Sub Hook(hwnd As Long)

   'Don't hook twice or you will
   'be unable to unhook it.
    If defWinProc = 0 Then
      defWinProc = SetWindowLong(hwnd, _
                                 GWL_WNDPROC, _
                                 AddressOf WindowProc)
    End If
End Sub

Public Function WindowProc(ByVal hwnd As Long, _
                           ByVal uMsg As Long, _
                           ByVal wParam As Long, _
                           ByVal lParam As Long) As Long
  'only if the window is the combo box...
   If hwnd = Form1.Combo1.hwnd Then
      Select Case uMsg
	'Case CBN_DROPDOWN  'the list box of a combo
        '                   'box is about to be made visible.
        '  'return 1 to indicate we ate the message
        '   WindowProc = 1
         Case WM_KEYDOWN   'prevent the F4 key from showing
                           'the combo's list
            If wParam = VK_F4 Then
              'set up the parameters as though a
              'mouse click occurred on the combo,
              'and call this routine again
               Call WindowProc(hwnd, WM_LBUTTONDOWN, 1, 1000)
              'there's nothing to do keyboard-wise
              'with the combo, so return 1 to
              'indicate we ate the message
               WindowProc = 1
            End If
         Case WM_LBUTTONDOWN  'process mouse clicks
           'if the listview is hidden, position and show it
            If Form1.ListView1.Visible = False Then
               With Form1
                  .ListView1.Left = .Combo1.Left
                  .ListView1.Width = .Combo1.Width
                  .ListView1.Top = .Combo1.Top + .Combo1.Height + 1
                  .ListView1.Visible = True
               End With
              'the listview must be visible, so hide it
               Form1.ListView1.Visible = False
            End If
           'return 1 to indicate we processed the message
            WindowProc = 1
         Case Else
           'call the default window handler
            WindowProc = CallWindowProc(defWinProc, _
                                        hwnd, _
                                        uMsg, _
                                        wParam, _
      End Select
   End If  'If hwnd = Form1.Combo1.hwnd
End Function
 Form Code
To a form, add a combo (Combo1), a listview control (ListView1), and three command buttons (Command1, Command2, Command3). Set the Style property of the Combo to Style 2, and add the following code to the form:

Option Explicit

Private bKeepOpen As Boolean

Private Sub Form_Load()

   Dim c As Long
   Dim chd As ColumnHeader
   Dim itmx As ListItem
  'Add some dummy data to the listview and hide
   With ListView1
      Set chd = .ColumnHeaders.Add(, , "Name", 1000)
      Set chd = .ColumnHeaders.Add(, , "Col 2", 1000)
      Set chd = .ColumnHeaders.Add(, , "Col 3", 1000)
      Set chd = .ColumnHeaders.Add(, , "Col 4", 600)
      For c = 1 To 15
         Set itmx = .ListItems.Add(, , Screen.Fonts(c))
         itmx.SubItems(1) = "screen"
         itmx.SubItems(2) = "font"
         itmx.SubItems(3) = c
      .View = lvwReport
      .FullRowSelect = True  'vb6 only
      .BorderStyle = ccNone
      .Visible = False
   End With

  'set inital state of command buttons
   Command1.Caption = "hook combo"
   Command2.Caption = "unhook combo"
   Command3.Caption = "unhook && end"
   Command1.Enabled = True
   Command2.Enabled = False   
End Sub

Private Sub Command1_Click()

   If defWinProc = 0 Then
      Hook Combo1.hwnd
      Command1.Enabled = False
      Command2.Enabled = True
   End If
End Sub

Private Sub Command2_Click()

  'unhook the combo
   If defWinProc <> 0 Then
      Unhook Combo1.hwnd
      defWinProc = 0
      Command1.Enabled = True
      Command2.Enabled = False      
   End If
End Sub

Private Sub Command3_Click()

   Unload Me

End Sub

Private Sub Form_Unload(Cancel As Integer)

   If defWinProc <> 0 Then Unhook Combo1.hwnd
End Sub

Private Sub ListView1_KeyDown(KeyCode As Integer, Shift As Integer)

  'set flag to allow arrow and enter
  'keys to simulate behaviour of normal
   bKeepOpen = True

End Sub

Private Sub ListView1_KeyPress(KeyAscii As Integer)

  'set flag to allow arrow and enter
  'keys to simulate behaviour of normal
   If KeyAscii = vbKeyReturn Then
     'simulate selecting item with enter
      bKeepOpen = False
      Call ListView1_Click
     'alpha or arrow keys being used,
     'so keep open
      bKeepOpen = True
   End If
End Sub

Private Sub ListView1_Click()

   Dim itmx As ListItem

   If ListView1.ListItems.Count > 0 Then

      Set itmx = ListView1.SelectedItem

     'For a style 0 combo, you can not assign
     'to the Text property from within the click
     'event, so the selected item must be 'added'
     'as the only combo item, and selected using
     'its listindex property.
     'For a style 2 combo, the text property
     'can't be set unless there is an exact
     'match to a list item, so again we fake it
     'by adding the selection to the combo and
     'selecting it.
     'Finally, since the tabs can't be used
     'in the combo's edit window, as it doesn't
     'support tabstops either, on selection we'll
     'display the main listview item
      With Combo1
         .AddItem itmx.Text
         .ListIndex = 0
      End With

   End If

   If bKeepOpen = False Then
      ListView1.Visible = False
   End If
End Sub
Save the project before running, and use Start with Full Compile to catch any errors that might crash the app when subclassing is enabled. Run the app and enable the hook, and try using the listview as you would the default combo list.

Note: In my testing code, I had populated the combo's normal list with a few items to see when the windowproc handled the notifications correctly. I found that when subclassed, the code would occasionally allow the display of this intrinsic list even when subclassed - some times instead of the substituted list, and sometimes in addition to it - as long as a subclassed list item had not been selected (clearing the intrinsic list and adding only the selected item to it). To prevent this 'error' from occurring, I added a trap for the CBN_DROPDOWN notification, ate the message, and this worked.

However there is a side effect to trapping this notification. Because it shares the same value as WM_SETFOCUS (&H7), including the trap prevents the user from tabbing between controls on the form when subclassing is enabled. In re-testing this, I've found the duplicate or erroneous display of the intrinsic list does not appear to occur when the intrinsic list is empty or only contains the single item from the previous selection. Therefore, when providing the user with the ability to tab between controls is required, leave the CBN_DROPDOWN code commented out. Thanks go to Forrest Cavalier III for pointing this out.


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