Previous | Index | Next 

[HOWTO] Migrate VB6 applications that use window subclassing

Even though VB Migration Partner can convert VB6 code that uses window subclassing (with some manual fixes on your part), you’d better off using one of the helper classes that the CodeArchitects.VBLibrary provides you with, the VB6WindowSubclasser class. This class enables you to implement subclassing in a simple and safe way and, just as important, without calling unmanaged code. Consider the following VB6 example that uses window subclassing:

' Some message constants.
Const WM_MOVE = &H3
Const WM_SIZING = &H214
Const WM_ACTIVATEAPP = &H1C

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
        ByVal hWnd As Long, ByVal ndx As Long, ByVal newValue As Long) 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
' This is used with the SetWindowLong API function.
Const GWL_WNDPROC = -4

Dim saveHWnd As Long        ' The handle of the subclassed window.
Dim oldProcAddr As Long     ' The address of the original window procedure

Sub StartSubclassing(ByVal hWnd As Long)
    saveHWnd = hWnd
    oldProcAddr = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WndProc)
End Sub

Sub StopSubclassing()
    SetWindowLong saveHWnd, GWL_WNDPROC, oldProcAddr
End Sub

Function WndProc(ByVal hWnd As Long, ByVal uMsg As Long, _
        ByVal wParam As Long, ByVal lParam As Long) As Long
    ' Send the message to the original window procedure, and then
    ' return Windows the return value from the original procedure.
    WndProc = CallWindowProc(oldProcAddr, hWnd, uMsg, wParam, lParam)
    
    ' process the message
    Select Case uMsg
        ' (omitted)
    End Select
End Function

The VB6WindowSubclasser’s constructor takes the handle of the window to be subclassed and a delegate to a method that handles incoming messages (WndProc in this case). You can stop subclassing by invoking the VB6WindowSubclasser.Dispose method. Thus, the following VB.NET code is equivalent to the previous VB6 code:

Dim subclasser As VB6WindowSubclasser

Sub StartSubclassing(ByVal hWnd As Long)
    subclasser = New VB6WindowSubclasser(hWnd, AddressOf WndProc)
End Sub

Sub StopSubclassing()
    subclasser.Dispose()
End Sub

The code inside the WndProc method is same as before, except that you invoke the original window procedure by calling the VB6WindowSubclasser.CallWindowProc method:

Function WndProc(ByVal hWnd As Long, ByVal uMsg As Long, _
        ByVal wParam As Long, ByVal lParam As Long) As Long
    ' Send the message to the original window procedure, and prepare to 
    ' return Windows the return value from the original procedure.
    WndProc = subclasser.CallWindowProc(hWnd, uMsg, wParam, lParam)
    
    ' process the message
    Select Case uMsg
        ' (omitted)
    End Select
End Function

You can implement this technique by editing the migrated VB.NET code or, even better, you can by adding some pragmas to the original VB6 code:

' Some message constants.
Const WM_MOVE = &H3
Const WM_SIZING = &H214
Const WM_ACTIVATEAPP = &H1C

'## ParseMode Off
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
        ByVal hWnd As Long, ByVal ndx As Long, ByVal newValue As Long) 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
' This is used with the SetWindowLong API function.
Const GWL_WNDPROC = -4

Dim saveHWnd As Long        ' The handle of the subclassed window.
Dim oldProcAddr As Long     ' The address of the original window procedure
'## ParseMode On
'## InsertStatement Dim subclasser As VB6WindowSubclasser

Sub StartSubclassing(ByVal hWnd As Long)
    '## ParseMode Off, 2
    saveHWnd = hWnd
    oldProcAddr = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WndProc)
    '## InsertStatement subclasser = New VB6WindowSubclasser(hWnd, AddressOf WndProc)
End Sub

Sub StopSubclassing()
    '## ReplaceStatement subclasser.Dispose()
    SetWindowLong saveHWnd, GWL_WNDPROC, oldProcAddr
End Sub

'## WndProc.MarkAsReferenced
Function WndProc(ByVal hWnd As Long, ByVal uMsg As Long, _
        ByVal wParam As Long, ByVal lParam As Long) As Long
    ' Send the message to the original window procedure, and then
    ' return Windows the return value from the original procedure.
    '## ReplaceStatement WndProc = subclasser.CallWindowProc(hWnd, uMsg, wParam, lParam)
    WndProc = CallWindowProc(oldProcAddr, hWnd, uMsg, wParam, lParam)
    
    ' process the message
    Select Case uMsg
        ' (omitted)
    End Select
End Function

 

Previous | Index | Next