Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By Mickey Williams and David Bennett

Creating Your Own Message Maps

When you create a class with AppWizard or ClassWizard, Visual C++ will produce the code to create a message map for your class. If you create your own CCmdTarget- derived class outside of ClassWizard, you need to create the message map yourself. You should start by adding the following line to the end of your class declaration:

DECLARE_MESSAGE_MAP()

This macro is defined in AFXWIN.H in the \DevStudio\VC\mfc\include directory. It declares the array that will hold your message map entries and some pointers used to find the message map of the base class. You should be aware, though, that the structures for your class's message map are defined static const. This means that you can have only one message map for all objects of your class, not a different map for each object. This also means that you cannot change a class's message map at runtime, at least not with methods that are discussed here. (It can be done with overrides of PreTranslateMessage() or the functions hidden in the message map itself.)

Next, you add a message map to your class implementation. To see how this is done, let's take a look at the message map implementation created for the CHiMomApp class in HiMom.cpp:

// CHiMomApp
BEGIN_MESSAGE_MAP(CHiMomApp, CWinApp)
    //{{AFX_MSG_MAP(CHiMomApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
       // NOTE - the ClassWizard will add and remove mapping macros here.
       //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    // Standard file based document commands
    ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
    // Standard print setup command
    ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()

As you can see in this example, message maps are created by a set of macros, beginning with BEGIN_MESSAGE_MAP() and ending with END_MESSAGE_MAP(). To start the definition of your message map, use the BEGIN_MESSAGE_MAP() macro, which takes the name of your class and the name of its base class as parameters.

The BEGIN_MESSAGE_MAP() macro (defined in AFXWIN.H) actually defines the _messageEntries array, leaving the initializer list open, to be filled by additional macros. If you forget to include the END_MESSAGE_MAP() macro, which closes the initializer list, the compiler will become quite lost, so make sure you end your message map with END_MESSAGE_MAP().

Populating the Message Map

In the previous example, you will see a DO NOT EDIT message from ClassWizard. It is a good idea to avoid editing these sections if you don't have to, but you can make changes here. However, you should be aware that changes to these blocks have the potential to confuse ClassWizard so that you cannot edit your class or must regenerate the .clw file, or ClassWizard may just overwrite your new changes. If you make changes to these blocks yourself, try to model your code after other ClassWizard-generated code and check the function of ClassWizard right after making your changes.

That said, you will be populating your message map by using several different macros for different types of messages, including message ranges.

Predefined Windows Message Handlers

For many standard Windows messages, there are predefined message map macros provided in AFXMSG.H. The names for these macros are derived directly from the message ID and they take no parameters. For example, the WM_PAINT message can be mapped by the ON_WM_PAINT() macro. This will map the WM_PAINT message to the OnPaint() function of your class. The other standard Windows messages are implemented in a similar fashion.

Other Windows Messages

For your own user-defined messages, or for windows commands that do not have a default handler, you can use the generic ON_MESSAGE() macro, which takes the message ID and handler function name:

ON_MESSAGE( WM_USER+1, OnMyUserMessage)

The handler for these messages would be declared like this:

afx_msg LRESULT OnMyUserMessage(WPARAM wParam, LPARAM lParam);

User-defined messages are generally given values between WM_USER and 0x7FFF. User-defined messages are most useful when sending messages within a single application. If you must send messages between applications, use registered Windows messages, which are discussed later in the section, "Registered Messages."

Command Messages

For command messages, you will use the ON_COMMAND() macro, which takes the command ID (which will be in the wParam of the WM_COMMAND message) and the name of the handler function:

ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

The handler function will take no parameters and return void:

afx_msg void OnFileNew();

Control Notifications

Notifications from controls can be mapped by using the ON_CONTROL() macro, which takes the control ID, the command ID, and the handler function as arguments:

ON_CONTROL( BN_CLICKED, IDC_MY_BUTTON, OnMyButtonClicked)

The handler for these messages, like command messages, returns void and takes no parameters:

afx_msg void OnMyButtonClicked();

Command and Control Ranges

There are also macros that will map a handler to messages for a range of commands or controls. This is one of the few areas where you must make your own message map entries, because ClassWizard doesn't handle ranges. Your entries for ranges would look like this:

ON_COMMAND_RANGE(ID_MY_FIRST_COMMAND, ID_MY_LAST_COMMAND, myCommandHandler)
ON_CONTROL_RANGE(BN_CLICKED, IDC_FIRST_BUTTON, IDC_LAST_BUTTON, MyButtonHandler)

Registered Messages

For message IDs that you have received from the RegisterWindowsMessage() function, you can use ON_REGISTERED_MESSAGE(), which takes the registered message ID and the handler function. The handler again returns void and takes no parameters:

afx_msg void OnExplorerRestarted();

Registered messages are used to enable multiple programs to exchange window messages without requiring applications to hard-code a specific value for the message. A registered message is defined by calling the RegisterWindowMessage function to determine a unique message number.

Registered messages can't be managed by ClassWizard. You'll need to manually add handlers for all registered messages that your application uses.

Other Message Map Macros

AFXMSG.H also defines many other macros designed to map different special cases of messages that you may find useful in creating your own message maps. For example, the ON_CONTROL() example used above can also be written like this:

ON_BN_CLICKED(IDC_MYBUTTON, OnMyButtonClicked)

This is actually the syntax that ClassWizard uses when it inserts entries like this, but it results in exactly the same entry in the message map as the ON_CONTROL() macro used above. There are also special macros for handling OLE functions and user interface updates that you will learn about later.

Inside the Message Map

The message map macros DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP, and END_MESSAGE_MAP are defined in AFXWIN.H. If you are curious, you can find this file in the VC98\MFC\INCLUDE subdirectory under the directory where Visual C++ was installed.

The DECLARE_MESSAGE_MAP macro is declared like this:

#ifdef _AFXDLL
#define DECLARE_MESSAGE_MAP() private:     static const AFX_MSGMAP_ENTRY _messageEntries[]; protected:     static AFX_DATA const AFX_MSGMAP messageMap;     static const AFX_MSGMAP* PASCAL _GetBaseMessageMap();     virtual const AFX_MSGMAP* GetMessageMap() const; 
#else
#define DECLARE_MESSAGE_MAP() private: }
    static const AFX_MSGMAP_ENTRY _messageEntries[]; protected:     static AFX_DATA const AFX_MSGMAP messageMap;     virtual const AFX_MSGMAP* GetMessageMap() const; #endif

In short, DECLARE_MESSAGE_MAP defines functions to return the class's message map (GetMessageMap()), and that of its base class (_GetBaseMessageMap()), as well as an AFX_MSGMAP structure. This structure consists primarily of an array of AFX_MSGMAP_ENTRY structures (_messageEntries[]).

The BEGIN_MESSAGE_MAP macro is defined like this:

#ifdef _AFXDLL
#define BEGIN_MESSAGE_MAP(theClass, baseClass)     const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap()         { return &baseClass::messageMap; }     const AFX_MSGMAP* theClass::GetMessageMap() const         { return &theClass::messageMap; }     AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap =     { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] };     AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =     { 
#else
#define BEGIN_MESSAGE_MAP(theClass, baseClass)     const AFX_MSGMAP* theClass::GetMessageMap() const         {  return &theClass::messageMap; }     AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap =     {  &baseClass::messageMap, &theClass::_messageEntries[0] };     AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =     { #endif

The BEGIN_MESSAGE_MAP macro implements the GetMessageMap() and _GetBaseMessageMap() functions, then begins initializing the _messageEntries[] array. The initializer list is left without a closing brace, leaving END_MESSAGE_MAP to add an entry that marks the end of the message map and closes the initializer list:

#define END_MESSAGE_MAP()         { 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }     }; 

Between the BEGIN_MESSAGE_MAP and END_MESSAGE_MAP macros, you use the message map entry macros, such as ON_COMMAND, which is actually defined like this:

#define ON_COMMAND(id, memberFxn)     { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },

The values specified in the message map macros are used to initialize an AFX_MSGMAP_ENTRY, which looks like this:

struct AFX_MSGMAP_ENTRY
{
    UINT nMessage;  // windows message
    UINT nCode;     // control code or WM_NOTIFY code
    UINT nID;       // control ID (or 0 for windows messages)
    UINT nLastID;   // used for entries specifying a range of control id's
    UINT nSig;      // signature type (action) or pointer to message #
    AFX_PMSG pfn;   // routine to call (or special value)
};

The first four elements are used by MFC to check whether this message map entry applies to the Windows message that is being dispatched. The last two elements specify information about the handler function to be called. pfn is a pointer to the function to call, and nSig is a special signature type, which MFC uses to specify the return type and parameters that will be passed to the function specified by pfn.

The values used for nSig are defined in the AfxSig enum type in Afxmsg_.h. This file also lists the function prototypes that should be used to correspond with each nSig value.

If you are ever unsure of what form a handler function for a given message map macro should take, you can look up the macro—for example, ON_COMMAND—in Afxmsg_.h, see that it uses AfxSig_vv, then find the definition of AfxSig_vv to find that your handler function should return void and take no parameters.

Using ClassWizard to add your handler functions will save you this trouble in most cases; however, there are certain message map entries that ClassWizard does not support, such as ON_COMMAND_RANGE or ON_THREAD_MESSAGE.

Implementing Handler Functions

When you add handler functions with ClassWizard, your handler function will be created for you, with the proper parameters and return type. If you create your message map en tries and corresponding handler functions yourself, be very careful to declare your handler function with the parameters and return type that the message map entry expects. Failure to do so will result in a corrupted stack at some point and can cause big headaches.

PreTranslateMessage()

In most cases, the message pump receives messages and dispatches them by way of message maps, as you saw in the previous example. However, if you find that you need to intercept messages prior to the normal dispatch process, MFC offers a way to do this—the PreTranslateMessage() function.

You can override the PreTranslateMessage() member function in any class derived from CWnd, CWinApp, or CWinThread. The function takes a pointer to an MSG structure and returns a BOOL. If PreTranslateMessage() returns TRUE, MFC will assume that you have handled the message. If it returns FALSE, MFC assumes that you have elected not to handle it and will proceed with its normal dispatch process.

+ Share This