แก้ไข

แชร์ผ่าน


TN006: Message Maps

This note describes the MFC message map facility.

The Problem

Microsoft Windows implements virtual functions in window classes that use its messaging facility. Due to the large number of messages involved, providing a separate virtual function for each Windows message would create a prohibitively large vtable.

Because the number of system-defined Windows messages changes over time, and because applications can define their own Windows messages, message maps provide a level of indirection that prevents interface changes from breaking existing code.

Overview

MFC provides an alternative to the switch statement that was used in traditional Windows-based programs to handle messages sent to a window. A mapping from messages to methods can be defined so that when a message is received by a window, the appropriate method is called automatically. This message-map facility is designed to resemble 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 put 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 by using a set of macros that expand to message map entries. A table starts 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_MESSAGE_NAME 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 keyword 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 by 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 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 put in an extern "C" block.

Note

ClassWizard will modify the message map entries that occur 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 method of the form:

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

    #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 that has a Windows message ID derived from the standard WM_USER base for user-defined messages. The following example shows how to call this handler:

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

The range of user-defined messages that use 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. ClassWizard will parse these entries and let you 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 name of a UINT NEAR 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 this example) must be a NEAR variable because of the way ON_REGISTERED_MESSAGE is implemented.

The range of user-defined messages that use 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. ClassWizard will parse these entries and let you 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 and a method. Only the specific WM_COMMAND message that has a wParam equal to the specified command ID is handled by the method specified in the message-map entry. Command handler member functions take no parameters and return void. The macro has the following form:

ON_COMMAND(id, memberFxn)

Command update messages are routed through the same mechanism, but use the ON_UPDATE_COMMAND_UI macro 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)

Advanced users can use the ON_COMMAND_EX macro, which is an extended form of command message handlers. The macro provides a superset of the ON_COMMAND functionality. Extended command-handler member functions take a single parameter, a UINT that contains the command ID, and return a BOOL. The return value should be TRUE to indicate that the command has been handled. Otherwise routing will continue to other command target objects.

Examples of these 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;
    }
    

Advanced users can handle a range of commands by using a single command handler: ON_COMMAND_RANGE or ON_COMMAND_RANGE_EX. See the product documentation for more information about 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 let you browse all four 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 the following conditions are true:

  • The control notification code (high word of lParam), such as BN_CLICKED, matches the notification code specified in the message-map entry.

  • The 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.

Note

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. ClassWizard will parse these entries and let you browse them just like any other message map entries.

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

See also

Technical Notes by Number
Technical Notes by Category