Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Windows Message Handling - Part 3

0.00/5 (No votes)
8 Jun 2000 1  
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 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
//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.

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