Using the MFC Message Map

In MFC, Windows-based messages can be sent to any window, including a view. Additionally, as described in Moving WM_COMMAND Code Into the View. MFC can route WM_COMMAND messages not only to window objects but also to documents and other "command target" objects. Windows-based WM_ messages arrive at their destinations in the usual way, but WM_COMMAND messages from menus, toolbar buttons, and accelerators follow a default routing from one object in the running application to another, until one of the objects claims the command and responds to it.

All of this ¾ both Windows-based message sending and command routing ¾ operates through MFC's "message map" mechanism. Message maps provide a table-based mechanism for connecting a particular message to a handler function designated to respond to that message. This mechanism is more efficient than C++ virtual functions, given the large number of Windows-based messages. In this way, MFC wraps messages just as it wraps APIs.

When an MFC object receives a message, it searches its message map (and the message maps of any classes it is derived from). If it finds a handler for the message, the object calls the handler.

A typical message map (located in the .CPP file for its class) looks something like this:


BEGIN_MESSAGE_MAP(CShowDibView, CView) //{{AFX_MSG_MAP(CShowDibView) ON_WM_TIMER() ON_COMMAND(ID_EDIT_COPY, OnEditCopy) //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP()

Between the beginning and ending macros, the message map contains one entry per mapped message. Each entry consists of an MFC macro with zero or more arguments. Note the entries for WM_TIMER and the first ON_COMMAND; you'll see example message handlers for these entries below. Entries you make via ClassWizard are placed between bracketing comments: "//{{AFX_MSG_MAP" and "//}}AFX_MSG_MAP." Entries made without ClassWizard should be added outside the comments.

A valuable additional migration step, then, is to move some or all of your message handling code into MFC message handlers. You use ClassWizard to create the handlers. The general process is simple:

Þ To move message handling code to an MFC message handler

1. Open ClassWizard, select the class you want to create a handler in, and specify the message to map.

ClassWizard creates a prototype for the handler function in the class declaration. It also creates the shell of the handler function, complete with the correct parameter signature.

2. Move your code for that message into the handler and do a little fix up, as illustrated for SHOWDIB below.

Class CWnd contains a member function for each Windows message. The names of these functions begin with "On" and provide default handling for the messages. When you create a new handler of the same name in one of your classes, the new handler overrides the default handler as if it were a virtual function (although in fact it is a message-mapped function instead).

For SHOWDIB, consider this example, a handler for the WM_TIMER message:


void CShowDibView::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default HWND hWnd = GetSafeHwnd(); /* Signal for palette animation */ hDC = ::GetDC(hWnd); hOldPal = ::SelectPalette(hDC, hpalCurrent, 0); { PALETTEENTRY peTemp; /* Shift all palette entries left by one position and wrap * around the first entry */ peTemp = pLogPal->palPalEntry[0]; for (i = 0; i < (pLogPal->palNumEntries - 1); i++) pLogPal->palPalEntry[i] = pLogPal->palPalEntry[i+1]; pLogPal->palPalEntry[i] = peTemp; } /* Replace entries in logical palette with new entries*/ ::AnimatePalette(hpalCurrent, 0, pLogPal->palNumEntries, pLogPal->palPalEntry); ::SelectPalette(hDC, hOldPal, 0); ::ReleaseDC(hWnd, hDC); /* Decrement animation count and terminate animation * if it reaches zero */ if (!(--nAnimating)) ::PostMessage(hWnd,WM_COMMAND,IDM_ANIMATE0,0L); CView::OnTimer(nIDEvent); }

The code moves over smoothly. The only things to clean up at this point are deleting the case and break statements, disambiguating Windows API calls, calling GetSafeHwnd, and making sure the variables and functions are visible within the chosen class — in this case the view class. At the bottom, note the additional call to the base class's version of OnTimer, to allow the base class to do any additional WM_TIMER processing it needs to do.

One important issue arises: how do you decide which class to map a message to? First, keep in mind that Windows-based messages other than WM_COMMAND must go to CWnd-derived objects. Second, use this rule of thumb — map each message to the class with the most contextual information for handling it. You probably want to handle commands related to the document as a whole, such as Save, in the document. You probably want to handle commands relating to selecting and editing data in the view. Commands that relate to the whole application probably should map to the application object. If handling a message in object A would require accessing lots of information in object B, maybe object B should handle that message.

Here is an additional example for SHOWDIB, mapping a WM_COMMAND message to the view object. The command is Edit Copy (related to selection), which generates a command whose ID is ID_EDIT_COPY:


void CShowDibView::OnEditCopy() { // TODO: Add your command handler code here HWND hWnd = GetSafeHwnd(); // if (!bLegitDraw) // lines removed // return 0L; /* Clean clipboard of contents */ if (bLegitDraw && ::OpenClipboard(hWnd)) { EmptyClipboard (); SetClipboardData (CF_DIB ,NULL); SetClipboardData (CF_BITMAP ,NULL); SetClipboardData (CF_PALETTE ,NULL); CloseClipboard (); } }

Again the code maps over very cleanly. OpenClipboard is the only clipboard function in Windows that MFC wraps, so it's the only one you need to disambiguate. You only need to make sure the bLegitDraw variable is visible and call GetSafeHwnd to use m_hWnd for the HWND parameter to OpenClipboard. One last change: because OnEditCopy doesn't return a value, check the bLegitDraw flag in the second if statement, as shown above, and delete the first if statement, now commented out.

Tip If you selected the Toolbar option in AppWizard, the default Copy button on the toolbar is enabled at this point. The button is associated with the same ID as the menu item; the same handler, OnEditCopy, handles both user interface objects.

Look again at the message map entries for the WM_TIMER and WM_COMMAND messages:


ON_WM_TIMER() ON_COMMAND(ID_EDIT_COPY, OnEditCopy)

The ON_WM_TIMER macro takes no parameters. For ON_COMMAND, though, it's necessary to specify which command ID is being handled and which handler function will do the job.

Using this technique with ClassWizard, you can easily map all of the cases in your WindowProc and OnCmdMsg functions to separate message handlers in a short time. Don't forget to delete WindowProc and OnCmdMsg when you finish mapping the last message. You'll take care of the extra WindowProc override in class CMainFrame (which you created in Handling Messages Not Sent to the View) in Using MFC Menu Update Handlers.

Tip In Visual C++ 2.0, use ClassWizard to delete the function when you've emptied it. In earlier versions of Visual C++, or other C++ products, you may need to do it by hand. If so, delete (a) the function declaration in the .H file for your view class, and (b) the function definition in the .CPP file.