article

Windows Messages and Subclassing

Email
Submitted on: 1/24/2015 4:32:00 PM
By: IRBMe (from psc cd)  
Level: Intermediate
User Rating: By 34 Users
Compatibility: VB 5.0, VB 6.0
Views: 720
 
     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
<--[if gte mso 9]>
 
 Christopher Waddell
 Christopher Waddell
 3
 94
 2002-04-25T17:06:00Z
 2002-04-26T15:50:00Z
 5
 1596
 9098
 Developement
 75
 18
 11172
 9.4402
 




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!

 

 

 

 

 

 

 

 

 

 

 

 

 


Other 21 submission(s) by this author

 


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments


 There are no comments on this submission.
 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.