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:
Collapse
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 2 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:
Collapse
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:
Collapse
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
Collapse
#include
//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":
Collapse
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 2 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 2 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:
Collapse
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 2 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:
Collapse
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:
Collapse
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 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:
Collapse
#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:
Collapse
#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:
Collapse
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, lets 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:
Collapse
static UINT WM_FIND = RegisterWindowMessage(TEXT("YOURAPP_FIND_MSG"));
//...
SendMessage(hWndFindWindow, WM_FIND, lParam, wParam);
License
0 comments