Subclassing
Sub-Classing
has two definitions. In the
object-oriented sense, a sub-class is
derived from a base class to extend or
change its functionality. In the
Non-object oriented sense, Sub-classing
is used to refer to a technique in which
you intercept a window's default message
queue. You cannot intercept every message
that is sent to a window in VB, because
you must have the window's handle in
order to intercept the messages. You'll
already have the window created before
you have access to the HWND property;
therefore you'll miss certain messages.
What Is It?
Windows is a message based OS. Every
window has a message queue. The
opperating system posts messages in this
queue for the window to process. The
messages range in type from the user
moving the mouse, to the window being
destroyed. All windows (underneath) are
sitting in a countinuosly running loop.
In this loop the window checks for
messages, then processes them. Consider
the following psudo code:
While (GetMessage(Message))
{
If Message = CloseWindow Then
If MessageBox("Are You Sure You Want
To Quit") = Yes Then
DoSomeCleanUpWork
End If
End If
}
VB handles messages a little
differently:
While (GetMessage(Message))
{
If Message = .... Then Call The VB's
Event For the Message
}
But not all messages are processed on
VB's low-level message loop. Some
messages that are rarely used have been
left out of VB becuase they slow the
applications generated with VB. The Key
Word here is Rarely. Some developers
would still like to find out when one of
these messages occur, So we must
sub-class. From VB 1.0 to VB 4.0 there
was no built in method for accessing the
messages that were left out. VB 5.0
introduced a new token called AddressOf.
The AddressOf operator gives us the
specific memory location to the entry
point of a function. When you create an
application in VB, the application is
loaded in to memory and executed. The
AddressOf operator gives you the Address
Of the entry point to the function in
memory. This memory location is required
by windows in order to invoke our
function for the new message processing
routine. The Following Information is not
yet fully verified by Microsoft: It seems
that the entry point to the function can
be retrieved by using the VarPtr(FunctionName).
This way VB keeps info in a structure
(or type in VB) on every function in the
application. One of these elements is the
offset to the entry point of the
function. This value can be retrieved (in
long functions) with Long Address =
VarPtr(Function) + 56 In VB 5, and Long
Address = VarPtr(Function) + 64 in VB 6.
(I haven't verified these offsets in vb 3
or 4, some one let me know?): End of
Non-Confirmed Info. Windows takes this
address and calls our function with it
every time there is a new message to be
processed. Windows requires our function
to have a specified number and type of
paramaters in our function so it can give
us info on the new message. Here's how
the function should look:
Public Function WindowProc(ByVal hWnd
As Long, ByVal uMsg As Long, ByVal wParam
As Long, ByVal lParam As Long)
The paramaters in the function are
given to us by windows. Here's what the
paramaters mean:
hWnd: Handle of the window thats
getting the new message
uMsg: The new message
wParam & lParam: additional info on
the message
Different messages are handled in
different ways, and there are literally
thousands of messages that windows
process. Hence this document is not going into
detail of how to handle them all, but
rather get you pointed in the right
direction on how to process those
messages. Messages are normally not
removed from the queue by your sub-class
routine, instead action is normally taken
on them and then processed by the default
handler. For example when we get a
message that says the form is about to
painted, we will call the default paint
routine first, then add our graphics. If
we add our graphic first, the default
routine will just over-write our graphic.
If we don't call the default routine, the
window will become distorted. This shows
the importance of the default window
procedure or DefWindowProc as the API
name.
How Do I Use It?
You'll need a few declerations in a
code 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)
I've created some routines to simplify
the interception process:
Dim PrevProc As Long
Public Sub HookForm(F As Form)
PrevProc = SetWindowLong(F.hWnd,
GWL_WNDPROC, AddressOf WindowProc)
End Sub
Public Sub UnHookForm(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
The PrevProc variable is declared on
the Heap or in the General section of the
code module. The routine HookForm needs a
little attention. Here we call the
SetWindowLong API. This API has several
uses, on of gives us the ability to
"re-route" the default windows
procedure (GWL_WNDPROC) to a different
procedure (AddressOf WindowProc) for a
specified window (F.hWnd). This function
returns us the address to the original
function (PrevProc). We must save this
address for use later. You must always
return the default window procedure to VB
before closing the form or your app will
crash, so we'll save it for later. The
UnHookForm routine is where we return the
procedure back to VB's default for the
form. This call will return us AddressOf
WindowProc like to line in HookForm,
but we dont need it. Our WindowProc
function (for right now) is simply taking
the messages and passing them on to VB's
original message handler. At this point
the only thing we've added to our
application is wait time. The VB message
handler is always going to be faster than
your VB message handler. Any additional
work is just going to slow our
application. Messages can be sent to our
form at thousands a second, so be very
cautious when intercepting them. You
should only sub-class when necissary, and
keep it simple. Windows requires
different return values for different
messages, so make sure you set your
function to the correct return value
before exiting the function. In this
example we've just set the value to
whatever value VB has set for the
message. In this example we are joing to
sub-class a single form. So we need to
create a form. Here's the code:
Private Sub Form_Load()
HookForm Me
End Sub
Private Sub Form_Unload(Cancel As
Integer)
UnHookForm Me
End Sub
That's all there is to it (almost).
Here we've successfully sub classed a
window, but it doesn't do anything. Next
we'll need to figure out which messages
we want to catch. In this example we'll
use the paint message or WM_PAINT. We're
going to draw a circle on the form every
time the window is painted. Here's the
code:
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)
If uMsg = WM_PAINT Then Form1.Circle
(100, 100), 100, vbRed
End Function
Make sure you compile this code and
run the .EXE. VB will crash (under most
circumstances) when you debug this code.
Microsoft has a nice utility that
simplifies debugging of sub-classed
windows. You can download the dbgwproc.dll from their
web site. Your application will also
crash if you cause recursive events. For
example If we substituted Form1.Circle
(100, 100), 100, vbRed With Form1.Refresh,
we would be causing another WM_PAINT
message to be sent to our routine, thus
recalling Form1.Refresh again.
At that point our message queue would
fill up with WM_PAINT messages and our
app would crash. Microsoft has documented
every message, it's purpose, it's
paramaters and it's expected return value
in the Platform SDK. Refer to it for
specific info on the avilible messages.
Conclusion
Sub-classing windows extendes
functionality to an application not
directly exposed from VB.
Sub-classing can cause buggy
applications if not used correctly.
Back to Tutorials - Main
|