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: KPDTeam@Allapi.net
'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!