Subclassing ofers great advantages to VB programmres. This article should teach you all about the message system Windows Uses, and how to implement it into your Visual Basic Programs.
Windows Programming Windows Programming
Part 1 – Messages
How does the Window Operating System know what you are doing? How does it know when you click, where you click and with what button you click? How does it know when you press a key, what key you pressed and what window you are typing in? There are many questions with only one simple answer. The answer being a message system.
There are many hundreds of common Windows messages, which include the left mouse click, the right mouse click and also the key down, and key up messages. There are other messages other than those used to indicate user input. There is also a message for instance that tells a window to repaint (or redraw) itself and also a timer message.
So how do applications receive these messages? The answer is a “window procedure”, although not official, it is generally agreed that it should be called “WindowProc”. The window procedure is a function that will be called every time a message is sent to that window. It must be declared as a public function in a module! It looks like this:
Public Function WindowProc(ByVal hwnd As Long, ByVal uMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
End Function
Parameters: -
hwnd – The window handle of your window. A window handle is a unique number, which is assigned to your window. Whenever you call an API function that wants to do something with your window, you must pass the hwnd property
uMsg – This is the number of the message that was sent your window. For example:
Public Const WM_DRAWCLIPBOARD = &H308 ‘Declare this message as a const, making it easier to deal with.
You would then use it like this:
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 windowproc
Select case uMsg
Case DRAWCLIPBOARD
‘The data in the clipboard has changed, so do something
‘Case ... Other messages go here
Case Else
WindowProc = CallWindowProc(PrevProc, hwnd, uMsg, wParam, lParam) ‘Process all those other messages that we don’t care about
End select
wParam/lParam – These are general parameters and can store pretty much any values including other sub-messages. If memory serves me correctly then the mouse move message comes with the X and Y coordinates of the mouse stored in the wParam and lParam parameters.
Now some of you may be thinking, “I hope I don’t have to process all of the hundreds of messages, my code could be thousands of lines long”. For those of you who weren’t, well you are now. The answer is thankfully no. There is a default window procedure that will carry out the basic commands like painting your window, resizing it, moving it, giving it focus, and all of the hundreds of other things.
We have a lot of control when it comes to messages. We can create our own messages, send messages to the system and look at all the messages in the message queue. Consider the following API functions:
Declare Function GetMessage Lib "user32" Alias "GetMessageA" (lpMsg As Msg, ByVal hWnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long) As Long
Declare Function TranslateMessage Lib "user32" (lpMsg As Msg) As Long
Declare Function DispatchMessage Lib "user32" Alias "DispatchMessageA" (lpMsg As Msg) As Long
Type POINTAPI
x As Long
y As Long
End Type
Type Msg
hWnd As Long
message As Long
wParam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type
Complicated looking isn’t it? We can use these API functions as follows:
Dim aMsg as Msg
Call GetMessage (aMsg, 0, 0, 0)
Call TranslateMessage (aMsg)
Call DispatchMessage (aMsg)
I think that is pretty self-explanatory.
VB has a built in message handler in its form object. This is where the events come from on your forms, and also the controls as well. These events are just generated whenever the corresponding messages are detected in the window Procedure. And the X and Y values in the MouseDown event for example are just extracted from the lParam and wParam arguments in the WindowProc function.
Now, why would you want to write our own message handler if VB already provides a perfectly good one?
a) VB hides a lot of the Messages from us
b) VB deals with some messages in a way that might not suit what we want
c) VB processes its messages before sending us the event. What if we don’t want it to do anything?
Let us consider the rather complicated topic of Winsock API. The way Winsock lets us know what is going on is through messages sent to our window’s message handler. However VB hides these ones from us. In order to see them, we will have to create a window procedure of our own.
Now, how do we tell windows to send messages to our new window procedure? Like so:
Private Declare Function GetWindowLong Lib _
"user32" Alias "GetWindowLongA" (ByVal hWnd _
As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib _
"user32" Alias "SetWindowLongA" (ByVal hWnd _
As Long, ByVal nIndex As Long, ByVal dwNewLong _
As Long) As Long
Those are 2 new API calls, one creates a window procedure, and the other returns the address of a window procedure given the hwnd (window handle remember)
So, to set up a window procedure, we do this:
Public Const GWL_WNDPROC = -4
Private Sub Form_Load() ‘Of course it doesn’t have to go in form load
PrevProc = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WindowProc)
End sub
You can replace the “AddressOf WindowProc” with the name you have given to your window procedure, but I suggest you keep the name to WindowProc. Also remember WindowProc must be a public Function, written with the correct parameters and everything, in a public Module.
This API call returns the handle to the previous window procedure if one exists
We must store a value into PrevProc so that we can return the default Window Procedure when we are finished. So, how do we return the previous window procedure? Like this:
Private Sub Form_Unload(Cancel as Integer) ‘Again, doesn’t have to be in Form_Unload
If PrevProc <> 0 Then
SetWindowLong hwnd, GWL_WNDPROC, PrevProc
PrevProc = 0
End If
End Sub
So now we know how to:
Create the WindowProc Function.
Set the WindowProc function as a window procedure.
Look for messages that we want.
Extract values from the lParam and wParam arguments.
Process all the other messages with the default handler.
Remove our window procedure.
Here is a small example taken from AllApi.Net
'Create a new project, add a module to it
'Add a command button to Form1
'In the form
Private Sub Form_Load()
'KPD-Team 1999
'URL: http://www.allapi.net/
'E-Mail: [email protected]
'Subclass this form
HookForm Me
'Register this form as a Clipboardviewer
SetClipboardViewer Me.hwnd
End Sub
Private Sub Form_Unload(Cancel As Integer)
'Unhook the form
UnHookForm Me
End Sub
Private Sub Command1_Click()
'Change the clipboard
Clipboard.Clear
Clipboard.SetText "Hello !"
End Sub
'In a module
'These routines are explained in our subclassing tutorial.
'http://www.allapi.net/vbtutor/subclass.php
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
Public Const WM_DRAWCLIPBOARD = &H308
Public Const GWL_WNDPROC = (-4)
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)
If uMsg = WM_DRAWCLIPBOARD Then
MsgBox "Clipboard changed ..."
End If
End Function
If you want, you can create your own windows messages. However, problems can arise. Imagine you use a message in a DLL as follows:
Const MYMSG = WM_USER + 7
However, lets then imagine that another DLL uses the exact same message for something completely different. Now to make matters worse, some poor person tries to use the two DLL’s in the same project. Let the errors and bugs and problems commence. Well, there is a way around this:
Declare Function RegisterWindowMessage Lib "user32" Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long
What this will do is allow you to create unique message numbers. Lets say you wanted to create your own message, you would do something like this:
MY_MESSAGE = RegisterWindowMessage (“MyUniqueString”)
This will assign MY_MESSAGE a new unique message number every time it is run. However, if you put this in a DLL then how will the applications using the DLL know what the number of your message is? They do EXACTLY the same thing as above. When they enter “MyUniqueString” into the lpString Parameter, because it already exists (it was originally made by your DLL remember), it will now return the number that it assigned to MY_MESSAGE. Consider the following example:
MESSAGE_ONE = RegisterWindowMessage (“MyFirstString”)
Msgbox “Your first new message is “ & MESSAGE_ONE
MEASSAGE_TWO = RegisterWindowMessage (“MySecondString”)
Msgbox “Your second new message is “ & MESSAGE_TWO
Msgbox “How do we retrieve message one? Like this: “ & RegisterWindowMessage (“MyFirstString”)
Msgbox “How do we retrieve message two? Like this: “ & RegisterWindowMessage (“MySecondString”)
Well, that’s the end of this tutorial. Let me just tell you that the technical name for this is called Sub classing, in case you ever hear it referred to as that.
I hope that after reading this you understand everything, however if there is anything you still don’t understand then visit http://www.AllAPI.net and search for one of the API declarations mentioned in the tutorials. Alternately, search for WindowProc, or Subclass. They should get you something.
I’d just like to say how long it took me to highlight all that code in its correct colouring, so if anybody has a good program to do that automatically, I’d be grateful!
Also, I know there are loads of people out there who know the ins and outs of Windows messaging, and have read this for whatever reason. I know I read tutorials on things I know inside out anyway. So, for any of you experts who have read this, any concerns with the tutorial (Misinformation, bugs in code, even typo’s), then I’d like to know, so leave a comment if you want.
I also like to know if I have helped people, and if so, how much. So some comments there wouldn’t go amiss.
Enjoy!