Visual Basic/Subclassing
Subclassing can be useful when you want to add functionality that is not directly supported by Visual Basic. To explain how this works we must first go through a little background theory.
All windows in Windows (for example a form, a button, a listbox, etc.) have a function that the operating system or other programs can call to communicate with the program. Windows can, for example, send messages about events such as the mouse pointer moving over the window, a key being pressed when the window has the focus and much more. Programs can also send messages that ask for information about the window; for example, the EM_GETLINECOUNT message asks a textbox to send back the number of lines in the text it holds. One can also define one's own functions.
To call these special functions you can use PostMessage, SendMessage or CallWindowProc (the last only if you know the address of the function).
Usually such a procedure looks something like this:
Public Function WindowProc(ByVal hwnd As Long, _ ByVal uMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long ' Your code here Select Case uMsg Case 0 ' React to message 0 Case 1 ' React to message 1 End Select WindowProc = 0 ' Return a value to the caller End Function
In this function hwnd is the handle of the window that the caller has tried to contact; uMsg is the message identifier which says what the call is about; wParam and lParam are used for whatever purpose the caller and window agree on. The handle, hwnd, is not an address but is used by Windows to find the address.
If, for example, we want to set the text that appears on the title bar of a form we can use the following code:
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ (ByVal hwnd As Long, _ ByVal wMsg As Long, _ ByVal wParam As Integer, _ ByVal lParam As Any) As Long Private Const WM_SETTEXT = &HC Private Sub Form_Load() SendMessage Me.hwnd, WM_SETTEXT, 0&, "This is a test" End Sub The receiver will get this message via its window function, which will look something like this: Public Function WindowProc(ByVal hwnd As Long, _ ByVal uMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long ' hwnd is now equal to Me.hwnd, ' uMsg is WM_SETTEXT, ' wParam is 0 ' lParam is the address of the text: "This is a test" ' It doesn't actually look like this of course, but this gives ' a good enough impression of what happens under the surface Select Case uMsg Case WM_SETTEXT Me.Caption = lParam 'Case ... '... many more here End Select End Function
Why Subclass
What is the point of subclassing?
Using this technique we can completely replace a program's own window function with our own. Then we can respond to the messages in ways that Visual Basic doesn't, we can decide to send the messages further to the original window function or not as we like, modifying them on the way in any way we please.
Example
To specify that our window function is to be used we use the API call SetWindowLong. Study the following example and put it in a base module:
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _ (ByVal hwnd As Long, _ ByVal nIndex As Long, _ ByVal dwNewLong As Long) As Long 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 Declare Function SetClipboardViewer Lib "user32" _ (ByVal hwnd As Long) As Long Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ (ByVal hwnd As Long, _ ByVal wMsg As Long, _ ByVal wParam As Integer, _ ByVal lParam As Any) As Long Public Const WM_SETTEXT = &HC Public Const GWL_WNDPROC = (-4) Private PrevProc As Long ' The address of the original window function Public Sub SubclassForm(F As Form) ' AddressOf WindowProc = finds the address of a function PrevProc = SetWindowLong(F.hwnd, GWL_WNDPROC, AddressOf WindowProc) End Sub Public Sub UnSubclassForm(F As Form) ' It is _very_ important that we restore the original window function, ' because VB will crash if we don't. SetWindowLong F.hwnd, GWL_WNDPROC, PrevProc End Sub Public Function WindowProc(ByVal hwnd As Long, _ ByVal uMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long Dim sTemp As String If uMsg = WM_SETTEXT Then ' Don't let the text get through, replace it with our own. Also, because all ' strings in VB are of the format UTF-16 (Unicode) and the receiving method ' expects a zero-terminated ASCII-string, it is necessary to convert it before ' passing it further down the chain. sTemp = StrConv("Subclassing" & Chr(0), vbFromUnicode) lParam = StrPtr(sTemp) ' get the address of our text End If ' Call the original function WindowProc = CallWindowProc(PrevProc, hwnd, uMsg, wParam, lParam) End Function
Add a form with a button cmdTest and add this code:
Private Sub cmdTest_Click() SendMessage Me.hwnd, WM_SETTEXT, 0&, "This is a test" End Sub Private Sub Form_Load() ' Start subclassing SubclassForm Me End Sub Private Sub Form_Unload(Cancel As Integer) ' WARNING: If you stop the project (for example with the stop button) without calling this, ' your program, as well as the VB IDE, will most likely crash. UnSubclassForm Me End Sub
When you click the cmdTest button, you'll see that the text that appears is not "This is a test", but "Subclassing".
| Previous: Windows_API | Contents | Next: External_Processes |