TN006: Message Maps

This note describes the MFC message map facility.

The Problem

Microsoft Windows implements what are essentially virtual functions in window classes using its messaging facility. Due to the large number of messages involved, providing a separate virtual function for each Windows message results in a prohibitively large vtable.

Also, since the number of system-defined Windows messages changes over time, and since a specific application may want to define some Windows messages of its own, the message-map mechanism provides a level of indirection that prevents interface changes from breaking existing code.

Overview

MFC provides an alternative to the switch statement used in traditional Windows programs to handle messages sent to a window. A mapping from messages to member-functions may be defined so that when a message is to be handled by a window, the appropriate member function is called automatically. This message-map facility is designed to be similar to virtual functions but has additional benefits not possible with C++ virtual functions.

Defining a Message Map

The DECLARE_MESSAGE_MAP macro declares three members for a class.

  • A private array of AFX_MSGMAP_ENTRY entries called _messageEntries,

  • A protected AFX_MSGMAP structure called messageMap that points to the _messageEntries array

  • A protected virtual function called GetMessageMap that returns the address of messageMap.

This macro should be placed in the declaration of any class using message maps. By convention, it is at the end of the class declaration. For example:

class CMyWnd : public CMyParentWndClass
{
    // my stuff...

protected:
    //{{AFX_MSG(CMyWnd)
    afx_msg void OnPaint();
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()
};

This is the format generated by AppWizard and ClassWizard when they create new classes. The //{{ and //}} brackets are needed for ClassWizard.

The message map's table is defined with a set of macros that expand to message map entries. A table begins with a BEGIN_MESSAGE_MAP macro call, which defines the class that is handled by this message map and the parent class to which unhandled messages are passed. The table ends with the END_MESSAGE_MAP macro call.

Between these two macro calls is an entry for each message to be handled by this message map. Every standard Windows message has a macro of the form ON_WM_xxx (where xxx is the name of the message) that generates an entry for that message.

A standard function signature has been defined for unpacking the parameters of each Windows message and providing type safety. These signatures may be found in the file AFXWIN.H in the declaration of CWnd. Each one is marked with the word afx_msg for easy identification.

Note   ClassWizard requires that you use the afx_msg keyword in your message map handler declarations.

These function signatures were derived using a simple convention. The name of the function always starts with "On". This is followed by the name of the Windows message with the WM_ removed and only the first letter of each word capitalized. The ordering of the parameters is wParam followed by LOWORD(lParam) then HIWORD(lParam). Unused parameters are not passed. Any handles that are wrapped by MFC classes are converted to pointers to the appropriate MFC objects. The following example shows how to handle the WM_PAINT message and cause the CMyWnd**::OnPaint** function to be called:

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
    //{{AFX_MSG_MAP(CMyWnd)
    ON_WM_PAINT()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

The message map table must be defined outside the scope of any function or class definition. It should not be placed within an extern "C" block.

Note   ClassWizard will edit the message map entries that are found between the //{{ and //}} comment bracket.

User Defined Windows Messages

User-defined messages may be included in a message map by using the ON_MESSAGE macro. This macro accepts a message number and a member function of the form:

    // inside the class declaration
    afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);

For example:
    #define WM_MYMESSAGE (WM_USER + 100)

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
    ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
END_MESSAGE_MAP()

In this example, we establish a handler for a custom message with a Windows message ID derived from the standard WM_USER base for user-defined messages. You might invoke this handler with code such as:

CWnd* pWnd = ...;
pWnd->SendMessage(WM_MYMESSAGE);

The range of user defined messages using this approach must be in the range WM_USER to 0x7fff.

Note   ClassWizard does not support entering ON_MESSAGE handler routines from the ClassWizard user interface: you must manually enter them from the Visual C++ editor. Once entered, ClassWizard will parse these entries and allow you to browse them just like any other message-map entries.

Registered Windows Messages

The ::RegisterWindowMessage function is used to define a new window message that is guaranteed to be unique throughout the system. The macro ON_REGISTERED_MESSAGE is used to handle these messages. This macro accepts a the name of a near UINT variable that contains the registered windows message ID. For example

class CMyWnd : public CMyParentWndClass
{
public:
    CMyWnd();

    //{{AFX_MSG(CMyWnd)
    afx_msg LRESULT OnFind(WPARAM wParam, LPARAM lParam);
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()
};

static UINT NEAR WM_FIND = RegisterWindowMessage("COMMDLG_FIND");

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
    //{{AFX_MSG_MAP(CMyWnd)
    ON_REGISTERED_MESSAGE(WM_FIND, OnFind)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

The registered Windows message ID variable (WM_FIND in the example above) must be a NEAR variable because of the way ON_REGISTERED_MESSAGE is implemented.

The range of user defined messages using this approach will be in the range 0xC000 to 0xFFFF.

Note   ClassWizard does not support entering ON_REGISTERED_MESSAGE handler routines from the ClassWizard user interface, you must manually enter them from the text editor. Once entered, ClassWizard will parse these entries and allow you to browse them just like any other message-map entries.

Command Messages

Command messages from menus and accelerators are handled in message maps with the ON_COMMAND macro. This macro accepts a command ID as well as a member function. Only the specific WM_COMMAND message with a wParam equal to the specified command ID is handled by the member function specified in the message-map entry. Command handler member functions take no parameters and return void. The macro has the form:

ON_COMMAND(id, memberFxn)

Command update messages are routed through the same mechanism as ON_COMMAND handlers. The ON_UPDATE_COMMAND_UI macro is used instead. Command update handler member functions take a single parameter, a pointer to a CCmdUI object, and return void. The macro has the form

ON_UPDATE_COMMAND_UI(id, memberFxn)

An extended form of command message handlers is available for advanced uses. The ON_COMMAND_EX macro is used instead and provides a superset of the ON_COMMAND functionality. Extended command-handler member functions take a single parameter, a UINT containing the command ID, and return a BOOL. The BOOL return should be TRUE to indicate that the command has been handled, otherwise routing will continue to other command target objects.

Examples of the above forms:

  • Inside RESOURCE.H (usually generated by Visual C++)

    #define    ID_MYCMD      100
    #define    ID_COMPLEX    101
    
  • Inside the class declaration

    afx_msg void OnMyCommand();
    afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI);
    afx_msg BOOL OnComplexCommand(UINT nID);
    
  • Inside the message map definition

    ON_COMMAND(ID_MYCMD, OnMyCommand)
    ON_UPDATE_COMMAND_UI(ID_MYCMD, OnUpdateMyCommand)
    ON_COMMAND_EX(ID_MYCMD, OnComplexCommand)
    
  • In the implementation file

    void CMyClass::OnMyCommand()
    {
        // handle the command
    }
    
    void CMyClass::OnUpdateMyCommand(CCmdUI* pCmdUI)
    {
        // set the UI state with pCmdUI
    }
    
    BOOL CMyClass::OnComplexCommand(UINT nID)
    {
        // handle the command
        return TRUE;
    }
    

Also available for advanced use is ON_COMMAND_RANGE and ON_COMMAND_RANGE_EX which allow you to handle a range of commands with a single command handler. See the product documentation for more information on these macros.

Note   ClassWizard supports creating ON_COMMAND and ON_UPDATE_COMMAND_UI handlers, but it does not support creating ON_COMMAND_EX or ON_COMMAND_RANGE handlers. However, Class Wizard will parse and allow you to browse all three command handler variants.

Control Notification Messages

Messages that are sent from child controls to a window have an extra bit of information in their message map entry: the control's ID. The message handler specified in a message map entry is called only if (1) the control notification code (high word of lParam), such as BN_CLICKED, matches the notification code specified in the message-map entry and (2) control ID (wParam) matches the control ID specified in the message-map entry.

Custom control notification messages may use the ON_CONTROL macro to define a message map entry with a custom notification code. This macro has the form

ON_CONTROL(wNotificationCode, id, memberFxn)

For advanced usage ON_CONTROL_RANGE can be used to handle a specific control notification from a range of controls with the same handler.

ClassWizard does not support creating an ON_CONTROL or ON_CONTROL_RANGE handler in the user interface; you must manually enter them with the text editor. Once entered, ClassWizard will parse these entries and allow you to browse them just like any other message map entries.

The Windows Common Controls make use of the more powerful WM_NOTIFY for complex control notifications. This version of MFC has direct support for this new message with the ON_NOTIFY and ON_NOTIFY_RANGE macros. See the product documentation for more information on these macros.

Technical Notes by NumberTechnical Notes by Category