Windows Message Handling - Part 3






4.85/5 (34 votes)
Jun 8, 2000

502632
Handling messages in SDK programs
Handling Messages in SDK applications
This article assumes you are familiar with creating a window in an SDK program. The Dialog part assumes you are familiar with creating modal and modeless dialog in a SDK program.
Handling messages in SDK applications is a totally different process than MFC. No ClassWizard
or macros to help you. No CWinApp
to implement the Message Loop for you. It's all up to you.
Windows Classes and Window Procedures
Window
"classes" in traditional programming for Windows define the characteristics of a "class" (not a C++ class) from which any number of windows can be created. This kind of class is a template or model for creating windows. In Windows, every window has a Window Class that defines the attributes of a window such as the window's icon, the window's background and the window's procedure. To create a Window
class, you call RegisterClass
that accepts a WNDCLASS
structure defining the properties of the Window
class. Every window must have a window
class, so typically, RegisterClass
is called in WinMain
.
Usually, the Message Loop is implemented as a basic while
loop:
MSG msg;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam;
The MSG
structure is a structure that holds all the information about the message: The window it was sent to, the message identifier, the two lParam
/wParam
parameters that come with the message, the time at which the message was sent, and the position of the mouse when the message was sent.
The call to GetMessage
tells windows to retrieve the first message in the Message Queue. If there is no message in the Message Queue, GetMessage
will not return until there is. The return value from GetMessage
depends on the message it retrieved: If it was a WM_QUIT
message it will return FALSE
, if it wasn't, it will return TRUE
. The TranslateMessage
function translates virtual-key messages into character messages. The character messages are posted to the calling thread's Message Queue, to be read the next time the thread calls the GetMessage
function. For example, if you get a WM_KEYDOWN
message, TranslateMessage
will add a WM_CHAR
message to your Message Queue. This is very useful because the WM_KEYDOWN
will only tell you what key has been pressed, not the character itself. A WM_KEYDOWN
for VK_A
could mean "a
" or "A
", depending on the state of the Caps Lock and Shift key. TranslateMessage
will do the work of checking if it should be capital for you. The call to DispatchMessage
will call the Window Procedure associated with the window that received the message. That's the SDK Message Loop in a nutshell.
A Window Procedure is a function called by the Message Loop. Whenever a message is sent to a window, the Message Loop looks at the window's Window Class and calls the Window Procedure passing the message's information. A Window Procedure is prototyped as:
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
The HWND
is the handle to the window that received the message. This parameter is important since you might create more than one window using the same window class. uMsg
is the message identifier, and the last 2 parameters are the parameters sent with the message.
Typically, a Window Procedure is implemented as a set of switch
statements, and a call to the default window procedure:
LRESULT CALLBACK WndProc (HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam) {
switch (uMsg)
{
case WM_CREATE:
//Do some initialization, Play a sound or what ever you want
return 0 ;
case WM_PAINT:
//Handle the WM_PAINT message
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
The switch
-case
block inspects the message identifier passed in the uMsg
parameter and runs the corresponding message handler. The PostQuitMessage
call will send a WM_QUIT
message to the Message Loop, causing GetMessage()
to return FALSE
, and the Message Loop to halt.
DefWindowProc
As I stated in Part 1, Windows should handle any message you don't handle. The call to DefWindowProc()
gives Windows a shot at the message. Some messages such as WM_PAINT
and WM_DESTROY
must be handled in your Window Procedure, and not in DefWindowProc
.
Putting It All Together: AllToGether.C
#include <windows.h>
//Declare the Window Procedure
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // pointer to command line
int nCmdShow // show state of window
){
static TCHAR lpszClassName[] = TEXT ("AllTogether") ;
HWND hwndMainWindow ;
MSG msg ;
WNDCLASS wndclass ;
//Fill in the Window class data
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
// The default window background
wndclass.hbrBackground = COLOR_WINDOW;
// The system, IDC_ARROW cursor
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
//The system IDI_APPLICATION icon
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hInstance = hInstance ;
wndclass.lpfnWndProc = WndProc ;
//The name of the class, needed for CreateWindow
wndclass.lpszClassName = lpszClassName;
wndclass.lpszMenuName = NULL ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
RegisterClass (&wndclass);
hwndMainWindow =
CreateWindow (lpszClassName, // pointer to registered class name
TEXT ("Lets Put it all together"), // pointer to window name
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, CW_USEDEFAULT, // position of window
CW_USEDEFAULT, CW_USEDEFAULT, // size of window
NULL, // handle to parent or owner window
NULL, // handle to menu
hInstance, // handle to application instance
NULL) ; // pointer to window-creation data
ShowWindow (hwnd, nCmdShow);
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam) {
switch (uMsg)
{
case WM_CREATE:
//Do some initialization, Play a sound or what ever you want
return 0 ;
case WM_PAINT:
//Handle the WM_PAINT message
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
Sending Messages
Besides receiving messages, you will often find yourself sending messages. You might want to send messages to communicate between two windows in your program, or to communicate between different programs. In order to send a message, you need a handle to the target window. This can be retrieved using a variety of functions, including FindWindow()
, GetDlgItem()
, GetParent()
, EnumWindows()
and many more. The SDK has a SendMessage()
function which allows you to send messages to a window. For example, let's say you have a handle to the Calculator
, and you want to close it. What you should do is send a WM_CLOSE
message, which will notify the Calculator
that it should close. You can use the following code. In order to get a pointer to Calculator
, I use the FindWindow()
function and pass the title of the window, which in our case is "Calculator
":
HWND hWndCalc;
//Get a handle to the "Calculator" Window
hWndCalc = FindWindow(NULL, TEXT("Calculator));
if(hWndCalc == NULL)
{
//Couldn't find Calculator
}
else
{
SendMessage(hWndCalc, WM_CLOSE, 0, 0);
//Presto! The Calculator should close.
}
LOWORD and HIWORD Macros: Split up lParam and wParam
Often, one or more of the 32-bit lParam
and wParam
parameters are actually made of two 16-bit parameters. One case is the WM_MOUSEMOVE
message. MSDN states that the lParam
for this message is actually two values: the X position of the mouse, and the Y position of the mouse. But how do you retrieve the values from the lParam
? The SDK has two macros designed for exactly this purpose: LOWORD()
and HIWORD()
. The LOWORD
macro retrieves the low-order word from the given 32-bit value, and the HIWORD()
macro retrieves the high-order word. So, given an lParam
of WM_MOUSEMOVE
, you can retrieve the coordinates using the following code:
WORD xPos = LOWORD(lParam); // horizontal position of cursor
WORD yPos = HIWORD(lParam); // vertical position of cursor
MAKELPARAM and MAKEWPARAM Macros: Concatenate Two 16-bit Values
LOWORD
and HIWORD
are fine if you want to split up the parameters, but what if you want to create a 32-bit value for use as an lParam
or wParam
parameter in a message? The SDK has two macros for this situation also: MAKELPARAM
and MAKEWPARAM
both combine two 16-bit values into a 32-bit value, that is usable for messages. For example, the following code sends a WM_MOUSEMOVE
message to a window (HWND hWndTarget
) with the fFlags
parameter as the wParam
, and the x/y coordinates as the lParam
:
SendMessage(hWndTarget, WM_MOUSEMOVE, fFlags, MAKELPARAM(x,y));
Dialogs
Handling a message in a dialog is very similar to handling a message in a normal window. Windows have Window Procedures, Dialogs have Dialog Procedures. One major difference is that you don't specify a window class for a dialog. When you create a dialog using one of the CreateDialog...
functions or the DialogBox...
functions, you pass a Dialog Procedure as one of the parameters. A Dialog Procedure is prototyped as:
BOOL CALLBACK DialogProc(
HWND hwndDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
You might have noticed that the Dialog Procedure looks very similar to the Window Procedure, but it isn't a real Window Procedure. The Window Procedure for the dialog is located inside windows. That Window Procedure calls your Dialog Procedure when various messages are sent to your window. Because of the above, there are messages that you will receive in a Window Procedure that you won't receive in a Dialog Procedure. There are a few major differences between a Window Procedure and a Dialog Procedure:
- A Dialog Procedure returns a
BOOL
, a Window Procedure returns aLRESULT
. - A Dialog Procedure doesn't need to handle
WM_PAINT
orWM_DESTROY
. - A Dialog Procedure doesn't receive a
WM_CREATE
message, but rather aWM_INITDIALOG
message. - A Window Procedure calls
DefWindowProc()
for messages it does not handle. A Dialog Procedure should returnTRUE
if it handled the message orFALSE
if not with one exception: if you set the input focus to a control inWM_INITDIALOG
, you should returnFALSE
.
User-Defined Messages
Sometimes, you will need to communicate between 2 windows in your application or between 2 windows from different applications. An easy way to do this is by using User-defined messages. The name "User-defined" can be confusing at first; you define a User-defined message and not the user of your program. I have stated in Part 1 that messages are identified by numbers, and that Windows predefines standard messages. The way of using user-defined messages is to simply use a number. To make sure that you don't conflict with the system defined messages, you should use a number in the range of WM_APP
through 0xBFFF:
#define WM_DELETEALL WM_APP + 0x100
//...
SendMessage(hWndYourDialog, WM_DELETEALL, 0, 0);
You handle a user-defined message just like you handle a regular message:
#define WM_DELETEALL WM_APP + 0x100
//Window Procedure
LRESULT CALLBACK WndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DELETEALL:
//We've got the user-defined message, lets Delete All
return 0;
case WM_CREATE:
//Do some initialization, Play a sound or what ever you want
return 0 ;
case WM_PAINT:
//Handle the WM_PAINT message
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
Registered Windows Messages
The RegisterWindowMessage
function is used to define a new window message that is guaranteed to be unique throughout the system. Like user-defined messages, Registered Messages are handled like regular messages:
static UINT WM_FIND = RegisterWindowMessage(TEXT("YOURAPP_FIND_MSG");
//Window Procedure
LRESULT CALLBACK WndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_FIND:
//We've got the registered message, let's start Finding.
return 0;
case WM_CREATE:
//Do some initialization, Play a sound or what ever you want
return 0 ;
case WM_PAINT:
//Handle the WM_PAINT message
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
The registered message identifiers using this approach will be in the range of 0xC000
to 0xFFFF
. And you send it using the regular SendMessage()
method:
static UINT WM_FIND = RegisterWindowMessage(TEXT("YOURAPP_FIND_MSG"));
//...
SendMessage(hWndFindWindow, WM_FIND, lParam, wParam);
History
- 9th June, 2000: Initial version
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.