MFC TN006--Message Maps

Created: April 15, 1992

ABSTRACT

This technical note describes the MFC message map facility.

The Microsoft Foundation Class (MFC) library provides a full-featured set of C++ object classes for the MicrosoftÒ WindowsÔ graphical environment. It includes classes that directly support application development for Windows as well as general-purpose classes for collections, files, persistent storage, exceptions, diagnostics, memory management, strings, and time. Each MFC technical note describes a feature of MFC using code fragments and examples.

THE PROBLEM

The MicrosoftÒ WindowsÔ graphical environment implements virtual functions in window classes using its messaging facility. Because a large number of messages are involved, providing a separate virtual function for each results in a prohibitively large virtual function table (v-table).

OVERVIEW

MFC provides an alternative to the switch statement usually used in Windows programs to handle messages sent to a window. You can map messages to member functions so that the appropriate member function is called automatically when a window handles a message. This message map facility was designed to be as similar to virtual functions as possible without a large v-table overhead.

DEFINING A MESSAGE MAP

The DECLARE_MESSAGE_MAP macro declares a private array for the message map entries called _messageEntries, a protected CMessageMap called messageMap, and a protected virtual function called GetMessageMap that returns the address of messageMap. Place this macro 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...

afx_msg void OnPaint();

DECLARE_MESSAGE_MAP()

};

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

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

For example:

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)

ON_WM_PAINT()

END_MESSAGE_MAP()

The message map table must be defined at file scope, outside the scope of any function or class definition. Do not place it within an extern “C” block.

A standard function signature has been defined for unpacking the parameters of each Windows message and providing type safety. These signatures are in the AFXWIN.H file in the declaration of CWnd. Each is marked with the “afx_msg” identifier.

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) and then HIWORD(lParam). Unused parameters are not passed. Any handles that MFC wraps are converted to pointers to the appropriate MFC objects.

USER-DEFINED MESSAGES

You can include user-defined messages in a message map by using the ON_MESSAGE macro. This macro accepts a message number and a member function in the form:

// Inside the class declaration:

afx_msg LONG OnMyMessage(UINT wParam, LONG lParam);

For example:

#define WM_MYMESSAGE WM_USER + 5

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 can call this handler with code such as:

extern CMyWnd myWnd;

myWnd->SendMessage(WM_MYMESSAGE);

REGISTERED WINDOWS MESSAGES

The ::RegisterWindowMessage function defines a new window message that is guaranteed to be unique throughout the system. The ON_REGISTERED_MESSAGE macro handles these messages. This macro accepts the name of a near UINT variable that contains the registered window’s message ID.

For example:

class CMyWnd : public CMyParentWndClass

{

public:

CMyWnd();

afx_msg LONG OnFind(UINT wParam, LONG lParam);

DECLARE_MESSAGE_MAP()

};

static UINT _near wm_Find = RegisterWindowMessage("commdlg_Find");

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)

ON_REGISTERED_MESSAGE(wm_Find, OnFind)

END_MESSAGE_MAP()

Note:

The registered window’s message ID variable (wm_Find in the example above) must be a _near variable because of the way ON_REGISTERED_MESSAGE is implemented. If your program runs in an ambient far data model (large or compact), you must explicitly qualify the variable declaration with the _near modifier, as shown above. You need not qualify the declaration if your program uses small or medium model, but it does not hurt to make the declaration explicit.

The MFC library AFX.H header file #defines NEAR to be _near.

USER-DEFINED 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 WM_COMMAND messages with a wParam equal to the ID match these table entries. The macro has the form:

ON_COMMAND(id, memberFxn)

Command handler member functions must take no parameters and must return void.

For example:

// Inside a resource header (usually generated by DLGEDIT):

#define IDM_MYCMD 100

// Inside the class declaration:

afx_msg void OnMyCommand();

// Inside the message map definition:

ON_COMMAND(IDM_MYCMD, OnMyCommand)

CONTROL NOTIFICATION MESSAGES

Messages that child controls send to a window have an extra bit of information in their message map entry: the control’s ID. The message map entry matches the message only if the ID in the entry matches the ID sent with the notification message.

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)

HOW A MESSAGE IS TRANSLATED

The WindowProc member function of the CWnd class is the workhorse of the message handler. It is the default window procedure for every window that MFC creates.

When a message is to be handled, the WindowProc function searches the message map attached to the window receiving the message for an appropriate entry. If it finds an entry, the wSig field of the entry calls the function pointer, pfn, in the entry with the appropriate signature.

If it does not find an entry, the message map of the window’s parent class is searched. This process continues until a handler is found or until the top of the CWnd class hierarchy is found, at which point the message is passed to DefWindowProc so that the system can perform any default processing.

A cache of recently handled messages is used to speed up searches through the message map.

Registered windows messages are handled as a special case of the general mechanism.

Note:

The message map parent class/child class relationship described above is established through the BEGIN_MESSAGE_MAP macro and not through the normal C++ language inheritance mechanism.

For example:

class CMyParentWnd : public CFrameWnd

{

...

DECLARE_MESSAGE_MAP()

};

BEGIN_MESSAGE_MAP(CMyParentWnd, CFrameWnd) // correct

...

END_MESSAGE_MAP()

class CMyWnd : public CMyParentWnd

{

...

DECLARE_MESSAGE_MAP()

};

BEGIN_MESSAGE_MAP(CMyWnd, CFrameWnd) // incorrect

...

END_MESSAGE_MAP()

The first message map is defined correctly. CFrameWnd is an immediate base class of CMyParentWnd, and the BEGIN_MESSAGE_MAP macro reflects this relationship.

The second message map is defined incorrectly. Although CFrameWnd is a base class of CMyWnd, it is not an immediate base class. If a message is sent to a CMyWnd object that the object does not know how to handle, the message is passed on to the CFrameWnd message map for processing. The message should have been passed to the CMyParentWnd message map first. This error cannot be caught at compile time; it will only be evident in run-time misbehavior.