Subclassing
This article is based on Visual Basic 6. Find other Visual Basic 6 articles. |
One of the most annoying things in VB6 is that it doesn't allow you any form of native method to handle the low-level windows messages, and thus we are forced to use a method called subclassing, where we use some specific functions to intercept the message queue for a particular window, in order to 'catch' the messages.
Note: This is NOT subclassing in the object-oriented sense that a class can be derived from a base class.
How to do it
The two basic functions that are used in subclassing are SetWindowLong and CallWindowProc -
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
In order to subclass a window, we must first call SetWindowLong to change it's Window Procedure (a callback function that handles the messages for the window) to one we define in our app (which MUST be done in a module). In the Window Procedure we must call CallWindowProc so that the old Window Procedure gets the messages and the window doesn't effectively freeze.
Once we have done all we need with the subclassing (usually when the form is unloaded), we must also unsubclass in order to stop our app and the VB IDE crashing.
The procedure can, therefore, be reduced to the following methods and declarations (in a 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 Public Const GWL_WNDPROC = (-4) Dim PrevProc As Long Public Sub SubclassForm(F As Form) PrevProc = SetWindowLong(F.hWnd, GWL_WNDPROC, AddressOf WindowProc) End Sub Public Sub UnSubclassForm(F As Form) 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 WindowProc = CallWindowProc(PrevProc, hWnd, uMsg, wParam, lParam) End Function
In the WindowProc function, you can then handle all the messages you need.
That simple?
In essence, yes, it is that simple. There are, however, a few drawbacks with the process.
The first drawback, which you will probably notice the most, is that if there is an error in the Window Procedure, then both your program and the VB IDE will crash. There is a way to get around that by using an external library to subclass, but if you can write code that has no errors then it's not worth it, though be prepared to crash the VB IDE quite a few times and to have to restart every now and then.
The second drawback is that, by the time you are able to subclass a window, it has already been created. This means that you will be unable to catch some of the messages that happen before you can access the hWnd property of a form, such as the WM_CREATE message. In such instances you need to use a rather horrid workaround involving hooking your app before any forms are loaded and looking for such messages from there.
The final, and possibly most major drawback to the system is that it is impossible to receive these messages as events in a class module. Well I say impossible... it's not really, but it is difficult, and advanced, and not suited to someone who is just starting subclassing. The method is described in the sample Modularised Subclassing.
That's all folks.