Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1530

[VB6] Subclassing With Common Controls Library

$
0
0
Subclassing... An advanced topic that has become much easier over the years. About the only thing that can be considered advanced nowadays is the added research subclassing requires to properly handle messages and retrieving structures and data related to some pointer the subclass procedures receives.

What is posted here is simply a working, drop-in, collection of code that can be added to any project. Subclassed messages can be received in a form, class, usercontrol or property page. The code provided is specifically designed for the subclassing functions provided by the common controls library (comctl32.dll). It does not require manifesting or adding the Windows Common Control ocx to your project. The provided code is targeted for projects, not stand-alone classes, therefore, requires the bas module and separate implementation class below.

Content of modSubclasser follows
Code:

'----- modSubclasser ---------------------------------------------------------------------
' This module can be added to any project. Its declarations are all private and should
'  not cause any conflicts with any existing code already in your project.
' To use this module to subclass windows, very little overhead is needed:
'  1) Add this module to your project
'  2) Add the ISubclassEvent class to your project
'  3) In whatever code page (form/class/usercontrol/propertypage) that you want to
'      receive subclassed messages, add this in the declarations section of the code page:
'      Implements ISubclassEvent
'  4) As needed, call the SubclassWindow() method in this module
'  5) When subclassing no longer needed, call the UnsubclassWindow() method
'-----------------------------------------------------------------------------------------

Option Explicit

' comctl32 versions less than v5.8 have these APIs, but they are exported via Ordinal
Private Declare Function SetWindowSubclassOrdinal Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
Private Declare Function DefSubclassProcOrdinal Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function RemoveWindowSubclassOrdinal Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
' comctl32 versions 5.8+ exported the APIs by name
Private Declare Function DefSubclassProc Lib "comctl32.dll" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function SetWindowSubclass Lib "comctl32.dll" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long

Private Declare Function IsWindow Lib "user32.dll" (ByVal hWnd As Long) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hWnd As Long, ByRef lpdwProcessId As Long) As Long
Private Declare Function DefWindowProcA Lib "user32.dll" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function DefWindowProcW Lib "user32.dll" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function IsWindowUnicode Lib "user32.dll" (ByVal hWnd As Long) As Long
Private Declare Sub RtlMoveMemory Lib "kernel32.dll" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
Private Declare Function LoadLibrary Lib "kernel32.dll" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32.dll" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function GetProcAddressOrdinal Lib "kernel32.dll" Alias "GetProcAddress" (ByVal hModule As Long, ByVal lpProcName As Long) As Long
Private Declare Function FreeLibrary Lib "kernel32.dll" (ByVal hLibModule As Long) As Long
Private Const WM_DESTROY As Long = &H2

Private m_SubclassKeys As Collection
Private m_UseOrdinalAliasing As Boolean

Public Function SubclassWindow(ByVal hWnd As Long, Receiver As ISubclassEvent, Optional ByVal Key As String) As Boolean
    ' can subclass multiple windows simultaneously
    ' see ISubclassEvent comments for helpful tips regarding the Receiver's event
   
    ' hWnd: The window handle & must be in the same process
    ' Receiver: The form/class/usercontrol/propertypage that Implements ISubclassEvent
    '  and wants to receive messages for the hWnd. Receiver MUST NOT be destroyed before
    '  all subclassing it is recieving are first released. If unsure, you should call
    '  the following in its Terminate or Unload event: UnsubclassWindow -1&, Me
    ' Key: unique key used to identify this specific instance of subclassing
    '  Key is passed to each subclass event and can be used to filter subclassed
    '  messages. Keys are unique per Receiver
    ' Recommend always assigning a key if subclassing multiple windows.
   
    ' Function fails in any of these cases:
    '  hWnd is not valid or is not in the same process as project
    '  Receiver is Nothing
    '  Key is duplicated
    '  Trying to subclass the same window twice with the same Receiver
   
    If Receiver Is Nothing Or hWnd = 0& Then Exit Function
    Dim lValue As Long
   
    Key = Right$("0000" & Hex(ObjPtr(Receiver)), 8) & Right$("0000" & Hex(hWnd), 8) & Key
    If m_SubclassKeys Is Nothing Then
        lValue = LoadLibrary("comctl32.dll")
        If lValue = 0& Then Exit Function      ' comctl32.dll doesn't exist
        m_UseOrdinalAliasing = False
        If GetProcAddress(lValue, "SetWindowSubclass") = 0& Then
            If GetProcAddressOrdinal(lValue, 410&) = 0& Then
                FreeLibrary lValue              ' comctl32.dll is very old
                Exit Function
            End If
            m_UseOrdinalAliasing = True
        End If
        FreeLibrary lValue
        Set m_SubclassKeys = New Collection
    Else
        On Error Resume Next
        lValue = Len(m_SubclassKeys(CStr(ObjPtr(Receiver) Xor hWnd)))
        If Err Then
            Err.Clear
        Else
            Exit Function                      ' duplicate key
        End If
        On Error GoTo 0
    End If
    If IsWindow(hWnd) = 0 Then Exit Function    ' not a valid window
    If Not GetWindowThreadProcessId(hWnd, lValue) = App.ThreadID Then Exit Function
   
    lValue = ObjPtr(Receiver) Xor hWnd
    m_SubclassKeys.Add Key, CStr(lValue)
    If m_UseOrdinalAliasing Then
        SetWindowSubclassOrdinal hWnd, AddressOf pvWndProc, lValue, ObjPtr(Receiver)
    Else
        SetWindowSubclass hWnd, AddressOf pvWndProc, lValue, ObjPtr(Receiver)
    End If
    SubclassWindow = True
   
End Function

Public Function UnsubclassWindow(ByVal hWnd As Long, Receiver As ISubclassEvent, Optional ByVal Key As String) As Boolean

    ' should be called when the subclassing is no longer needed
    ' this will be called automatically if the subclassed window is about to be destroyed
    ' To remove all subclassing for the Reciever, pass hWnd as -1&

    ' Function fails in these cases
    '  hWnd was not subclassed or is invalid
    '  Receiver did not subclass the hWnd
    '  Key is invalid

    Dim lID As Long, lRcvr As Long
    If Receiver Is Nothing Or hWnd = 0& Then Exit Function
   
    lRcvr = ObjPtr(Receiver)
    If hWnd = -1& Then
        For lID = m_SubclassKeys.Count To 1& Step -1&
            If CLng("&H" & Left$(m_SubclassKeys(lID), 8)) = lRcvr Then
                hWnd = CLng("&H" & Mid$(m_SubclassKeys(lID), 9, 8))
                Call UnsubclassWindow(hWnd, Receiver, Mid$(m_SubclassKeys(lID), 17))
            End If
        Next
        UnsubclassWindow = True
        Exit Function
    End If
   
    On Error Resume Next
    lID = lRcvr Xor hWnd
    Key = Right$("0000" & Hex(lRcvr), 8) & Right$("0000" & Hex(hWnd), 8) & Key
    If Key = m_SubclassKeys(CStr(lID)) Then
        If Err Then
            Err.Clear
            Exit Function
        End If
        If m_UseOrdinalAliasing Then
            lID = RemoveWindowSubclassOrdinal(hWnd, AddressOf pvWndProc, lID)
        Else
            lID = RemoveWindowSubclass(hWnd, AddressOf pvWndProc, lID)
        End If
        If lID Then
            UnsubclassWindow = True
            m_SubclassKeys.Remove CStr(lRcvr Xor hWnd)
            If m_SubclassKeys.Count = 0& Then Set m_SubclassKeys = Nothing
        End If
    End If
End Function

Private Function pvWndProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, _
                            ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
   
    Dim lAction As enumSubclassActions, bRtn As Boolean, sKey As String
    Dim IReceiver As ISubclassEvent, tObj As Object
   
    sKey = Mid$(m_SubclassKeys(CStr(uIdSubclass)), 17)
    RtlMoveMemory tObj, dwRefData, 4&
    Set IReceiver = tObj
    RtlMoveMemory tObj, 0&, 4&
   
    pvWndProc = IReceiver.ProcessMessage(sKey, hWnd, uMsg, wParam, lParam, lAction, bRtn, 0&)
    If uMsg = WM_DESTROY Then
        lAction = scevForwardMessage
        bRtn = False
        UnsubclassWindow hWnd, IReceiver, sKey
    End If
   
    If lAction = scevDoNotForwardEvent Then
        Exit Function
    ElseIf lAction = scevForwardMessage Then
        If m_UseOrdinalAliasing Then
            pvWndProc = DefSubclassProcOrdinal(hWnd, uMsg, wParam, lParam)
        Else
            pvWndProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
        End If
    ElseIf IsWindowUnicode(hWnd) Then
        pvWndProc = DefWindowProcW(hWnd, uMsg, wParam, lParam)
    Else
        pvWndProc = DefWindowProcA(hWnd, uMsg, wParam, lParam)
    End If
   
    If bRtn Then Call IReceiver.ProcessMessage(sKey, hWnd, uMsg, wParam, lParam, scevDoNotForwardEvent, True, (pvWndProc))
   
End Function

Content of ISubclassEvent follows
Code:

'----- ISubclassEvent ---------------------------------------------------------------------
'  Ensure this class is named ISubclassEvent
'-----------------------------------------------------------------------------------------

Option Explicit

Public Enum enumSubclassActions
    scevForwardMessage = 0    ' continue the message down the subclassing chain
    scevSendToOriginalProc = 1 ' skip the chain & send message directly to original window procedure
    scevDoNotForwardEvent = -1 ' do not forward this message any further down the chain
End Enum

Public Function ProcessMessage(ByVal Key As String, ByVal hWnd As Long, ByVal Message As Long, _
                ByRef wParam As Long, ByRef lParam As Long, ByRef Action As enumSubclassActions, _
                ByRef WantReturnMsg As Boolean, ByVal ReturnValue As Long) As Long

' Key. The Key provided during the SubclassWindow() call
' hWnd. The subclassed window's handle
' Message. The message to process
' wParam & lParam. Message-specific values
' Action. Action to be taken after you process this message
' WantReturnMsg. Set to True if want to monitor the result after message completely processed
' ReturnValue. The final result of the message and passed only when WantReturnMsg = True

' Notes
'  WantReturnMsg. This parameter serves two purposes:
'  1) Indication whether this message is received BEFORE other subclassers have received
'      it or AFTER the last subclasser has processed the message.
'      If parameter = False, this is a BEFORE event
'      If parameter = True, this is an AFTER event
'  2) Allows you to request an AFTER event. Set parameter to True during the BEFORE event.
'  Parameter is ignored if Action is set to scevDoNotForwardEvent in the BEFORE event.
'  When WantReturnMsg is set to True, after the subclassing chain processes the
'      message, you will get a second event. The WantReturnMsg  parameter will be True
'      and the ReturnValue parameter will contain the final result. This is the AFTER event.

'  wParam & lParam can be changed by you. Any changes are forwarded down the chain as necessary

'  Key parameter, if set, is very useful if subclassing multiple windows at the same time.
'  All subclassed messages for the same object implementing this class receives all messages
'  for each subclassed window thru this same event. To make it simpler to determine which
'  hWnd relates to what type of window, the Key can be used.

'  The return value of this function is only used if Action is set to scevDoNotForwardEvent
End Function

A simple sample. Have form subclass one of its textboxes
Code:

Option Explicit
Implements ISubclassEvent

Private Sub cmdSubclass_Click()
    SuclassWindow Text1.hWnd, Me, "txt1"
End Sub
Private Sub cmdUnSubclass_Click()
    UnsubclassWindow Text1.hwnd, Me, "txt1"
End Sub
Private Function ISubclassEvent_ProcessMessage(ByVal Key As String, ByVal hWnd As Long, _
                    ByVal Message As Long, wParam As Long, lParam As Long, _
                    Action As enumSubclassActions, WantReturnMsg As Boolean, _
                    ByVal ReturnValue As Long) As Long

    Select Case Message
        ...
    End Select
End Function

Side note. I have created several versions of IDE-safe subclassing routines over the years and all but two were based off of Paul Caton's ideas/code that used assembly thunks as a go-between. So I do have lots of experience with subclassing. The functions provided in comctl32.dll are theoretically IDE-safe. I personally find that the IDE is more responsive with the thunk version vs. these comctl32 functions. No code is truly IDE-safe if it is poorly written. As always, save often when debugging while subclassing. These comctl32 functions do make setting up subclassing a breeze.

Edited: Changed keying to allow unsubclassing all windows by a specific Receiver, at once. Useful should you want to terminate subclassed hWnds in one call vs. one at a time. Other minor tweaks were also made. FYI: Keys are in this format: [8 chars][8 chars][key] where 1st 8 chars is Hex value of Receiver, 2nd 8 chars is Hex value of subclassed hWnd & the [key] is the user-provided key, if any. This Key structure allows unsubclassing all windows with only knowing the Receiver and/or unsubclassing a hWnd without knowing the Receiver(s) that subclassed it.

If needed, you can add this to the module to retrieve the Key you assigned to a specific instance of subclassing:
Code:

Public Function GetSubclassKey(ByVal hWnd As Long, Receiver As ISubclassEvent) As String
    On Error Resume Next
    GetSubclassKey = Mid$(m_SubclassKeys(CStr(ObjPtr(Receiver) Xor hWnd)), 17)
    If Err Then Err.Clear
End Function


Viewing all articles
Browse latest Browse all 1530

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>