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,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
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:
return 0 ;
case WM_PAINT:
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>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
){
static TCHAR lpszClassName[] = TEXT ("AllTogether") ;
HWND hwndMainWindow ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hbrBackground = COLOR_WINDOW;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hInstance = hInstance ;
wndclass.lpfnWndProc = WndProc ;
wndclass.lpszClassName = lpszClassName;
wndclass.lpszMenuName = NULL ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
RegisterClass (&wndclass);
hwndMainWindow =
CreateWindow (lpszClassName,
TEXT ("Lets Put it all together"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
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:
return 0 ;
case WM_PAINT:
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;
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);
WORD yPos = HIWORD(lParam);
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,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
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 a LRESULT
. - A Dialog Procedure doesn't need to handle
WM_PAINT
or WM_DESTROY
. - A Dialog Procedure doesn't receive a
WM_CREATE
message, but rather a WM_INITDIALOG
message. - A Window Procedure calls
DefWindowProc()
for messages it does not handle. A Dialog Procedure should return TRUE
if it handled the message or FALSE
if not with one exception: if you set the input focus to a control in WM_INITDIALOG
, you should return FALSE
.
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
LRESULT CALLBACK WndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DELETEALL:
return 0;
case WM_CREATE:
return 0 ;
case WM_PAINT:
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");
LRESULT CALLBACK WndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_FIND:
return 0;
case WM_CREATE:
return 0 ;
case WM_PAINT:
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.