The Message Map

[Previous] [Next]

To enable us to process window messages in a CWindowImpl-derived class, ATL inherits from the abstract base class CMessageMap. CMessageMap declares one pure virtual function, ProcessWindowMessage. The entire class is shown here:

 class ATL_NO_VTABLE CMessageMap { public: virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg,     WPARAM wParam, LPARAM lParam,     LRESULT& lResult, DWORD dwMsgMapID) = 0; }; 

Your CWindowImpl-derived class must implement the ProcessWindowMessage function, which is called from the WindowProc function in the CWindowImpl base class CWindowImplBaseT. If ProcessWindowMessage returns TRUE, the message has been handled by your derived class and WindowProc shouldn't continue with default message processing. A FALSE return value allows default processing.

Message-Map Macros

ATL provides a set of macros that implement the ProcessWindowMessage function disguised as a message map. You don't need to actually write a ProcessWindowMessage function unless the message-map macros don't provide you with enough extensibility. The message map begins with BEGIN_MSG_MAP(className) and ends with END_MSG_MAP. These two macros expand to the following implementation of ProcessWindowMessage:

 BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam,     LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) {     BOOL bHandled = TRUE;     hWnd;     uMsg;     wParam;     lParam;     lResult;     bHandled;     switch(dwMsgMapID)     {     case 0:         break;     default:         ATLTRACE2(atlTraceWindowing, 0,             _T("Invalid message map ID (%i)\n"), dwMsgMapID);         ATLASSERT(FALSE);         break;     }     return FALSE; } 

The top section of this code is from the BEGIN_MSG_MAP macro, and the bottom (after case 0) is from END_MSG_MAP. ProcessWindowMessage switches on the message-map ID, which is 0 by default. The ID is there so that message handlers can be logically grouped. By default, all message handlers will go into the case 0 section. Later in the chapter, you'll find out how to add other groupings (case statements) using alternate message maps. To add a message handler, you can insert a simple if statement into the case 0 section:

 if(uMsg == WM_DESTROY) {     bHandled = TRUE;     lResult = OnDestroy(uMsg, wParam, lParam, bHandled);     if(bHandled)         return TRUE; } 

With the code shown here and an OnDestroy function that you define, you can handle the WM_DESTROY message. The OnDestroy function looks like this:

 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam,     BOOL& bHandled) {     // Do something here.     bHandled = FALSE;     return 0; } 

To handle a different message, you can just insert another if block exactly like the one for WM_DESTROY except with a different message and handler function. Your parameterized code alarm should be going off about now, since the only things that would change are the message ID and the handler function. ATL defines a set of macros that generate the if blocks needed to pair messages to handler functions. The complete ProcessWindowMessage implementation with the WM_DESTROY if block shown in the previous section is generated by the following macros:

 BEGIN_MSG_MAP(CMainFrame)     MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() 

Message handlers are processed in order from top to bottom until something handles the message or processing falls through, which results in default processing. The bHandled parameter is set to TRUE by default before the handler function is called. You can manually set it to FALSE (as OnDestroy does) to allow default processing after the handler function returns and ProcessWindowMessage exits.

ATL has a large selection of message-handler macros to choose from. The basic types are MESSAGE_HANDLER, NOTIFY_HANDLER, and COMMAND_HANDLER for normal window messages, WM_NOTIFY messages, and WM_COMMAND messages. Ranges of messages are handled using the corresponding macros MESSAGE_RANGE_HANDLER, NOTIFY_RANGE_HANDLER, and COMMAND_RANGE_HANDLER. The easiest way to add handlers to a message map is to right-click on the class in ClassView and choose Add Windows Message Handler from the context menu. Microsoft Visual C++ then inserts the correct macro based on the message you choose to handle. You can't use ClassWizard to add handlers to ATL message maps. Here's a summary of the available message-handler macros:

  • MESSAGE_HANDLER Maps a window message to a handler function
  • MESSAGE_RANGE_HANDLER Maps a contiguous range of window messages to a handler function
  • COMMAND_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code and the identifier of the menu item, control, or accelerator
  • COMMAND_ID_HANDLER Maps a WM_COMMAND message to a handler function based on the identifier of the menu item, control, or accelerator
  • COMMAND_CODE_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code
  • COMMAND_RANGE_HANDLER Maps a contiguous range of WM_COMMAND messages to a handler function based on the identifier of the menu item, control, or accelerator
  • NOTIFY_HANDLER Maps a WM_NOTIFY message to a handler function based on the notification code and the control identifier
  • NOTIFY_ID_HANDLER Maps a WM_NOTIFY message to a handler function based on the control identifier
  • NOTIFY_CODE_HANDLER Maps a WM_NOTIFY message to a handler function based on the notification code
  • NOTIFY_RANGE_HANDLER Maps a contiguous range of WM_NOTIFY messages to a handler function based on the control identifier

Message Reflection

Windows often need to process their own reflected messages. For example, a tab control might want to handle its tab drawing by processing WM_DRAWITEM messages. ATL message reflection requires a window's parent window to reflect messages back to the child using the REFLECT_NOTIFICATIONS macro. You can put this macro after any other standard message-handler macros in the parent, as shown here:

 BEGIN_MSG_MAP(CMyDialog)     MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)     MESSAGE_HANDLER(WM_RBUTTONUP, OnRButtonUp)     COMMAND_ID_HANDLER(IDOK, OnOK)     COMMAND_ID_HANDLER(IDCANCEL, OnCancel)     NOTIFY_HANDLER(IDC_TAB1, TCN_SELCHANGING, OnSelchangingTab1)     MESSAGE_HANDLER(WM_DESTROY, OnDestroy)     REFLECT_NOTIFICATIONS(); END_MSG_MAP() 

REFLECT_NOTIFICATIONS must be present in the parent window's message map to reflect messages back to the child window. The macro expands to call the ReflectNotifications function in CWindowImplRoot. ReflectNotifications validates that the message is important enough to be reflected and then sends the message on to the child with SendMessage. Before sending the message to the child, ATL adds the constant OCM_ _BASE to the message so that the handler in the child can determine it is a reflected message. OCM_ _BASE isn't defined by ATL, but it is defined in olectrl.h. A partial listing of ReflectNotifications is shown here:

 template <class TBase> LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg,     WPARAM wParam, LPARAM lParam, BOOL& bHandled) {     HWND hWndChild = NULL;     switch(uMsg)     {     case WM_COMMAND:         if(lParam != NULL) // Not from a menu             hWndChild = (HWND)lParam;         break;     case WM_NOTIFY:         hWndChild = ((LPNMHDR)lParam)->hwndFrom;         break;     case WM_PARENTNOTIFY:         switch(LOWORD(wParam))         {         case WM_CREATE:         case WM_DESTROY:             hWndChild = (HWND)lParam;             break;         default:             hWndChild = GetDlgItem(HIWORD(wParam));             break;         }         break;     case WM_DRAWITEM:         if(wParam) // Not from a menu             hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;     break;      // More messages omitted here     default:         break;     }     if(hWndChild == NULL)     {         bHandled = FALSE;         return 1;     }     ATLASSERT(::IsWindow(hWndChild));     return ::SendMessage(hWndChild, OCM_ _BASE + uMsg, wParam, lParam); } 

The child window handles the reflected message using the standard MESSAGE_HANDLER macros using the message IDs defined in olectrl.h for reflected messages, which are listed here:

 #define OCM_ _BASE           (WM_USER+0x1c00) #define OCM_COMMAND         (OCM_ _BASE + WM_COMMAND) #ifdef _WIN32 #define OCM_CTLCOLORBTN     (OCM_ _BASE + WM_CTLCOLORBTN) #define OCM_CTLCOLOREDIT    (OCM_ _BASE + WM_CTLCOLOREDIT) #define OCM_CTLCOLORDLG     (OCM_ _BASE + WM_CTLCOLORDLG) #define OCM_CTLCOLORLISTBOX (OCM_ _BASE + WM_CTLCOLORLISTBOX) #define OCM_CTLCOLORMSGBOX  (OCM_ _BASE + WM_CTLCOLORMSGBOX) #define OCM_CTLCOLORSCROLLBAR  (OCM_ _BASE + WM_CTLCOLORSCROLLBAR) #define OCM_CTLCOLORSTATIC  (OCM_ _BASE + WM_CTLCOLORSTATIC) #else #define OCM_CTLCOLOR        (OCM_ _BASE + WM_CTLCOLOR) #endif #define OCM_DRAWITEM        (OCM_ _BASE + WM_DRAWITEM) #define OCM_MEASUREITEM     (OCM_ _BASE + WM_MEASUREITEM) #define OCM_DELETEITEM      (OCM_ _BASE + WM_DELETEITEM) #define OCM_VKEYTOITEM      (OCM_ _BASE + WM_VKEYTOITEM) #define OCM_CHARTOITEM      (OCM_ _BASE + WM_CHARTOITEM) #define OCM_COMPAREITEM     (OCM_ _BASE + WM_COMPAREITEM) #define OCM_HSCROLL         (OCM_ _BASE + WM_HSCROLL) #define OCM_VSCROLL         (OCM_ _BASE + WM_VSCROLL) #define OCM_PARENTNOTIFY    (OCM_ _BASE + WM_PARENTNOTIFY) #if (WINVER >= 0x0400) #define OCM_NOTIFY            (OCM_ _BASE + WM_NOTIFY) #endif 

These IDs are used directly in the message map of the child window receiving reflected messages. For example, to handle a reflected WM_DRAWITEM message, the message map looks like this:

 BEGIN_MSG_MAP(CInsideAtlTabs)     MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)     DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() 

The DEFAULT_REFLECTION_HANDLER macro is placed in the message map of the child window to enable default processing for reflected messages.

Alternate Message Maps

Recall that the default message-map ID declared in BEGIN_MSG_MAP is 0. All message handlers are placed in map zero by default. You can declare additional maps using the ALT_MSG_MAP(msgMapID) macro. You might do this to enable a single class to process messages from more than one window. Because ProcessWindowMessage takes the message-map ID as a parameter (0 by default), the caller can easily specify a different map ID that corresponds to an ALT_MSG_MAP entry. ALT_MSG_MAP simply adds another case to the switch statement in ProcessWindowMessage. Here's what the macro definition looks like:

 #define ALT_MSG_MAP(msgMapID) \     break; \     case msgMapID: 

You can specify any number of alternate message maps between the BEGIN_MSG_MAP and END_MSG_MAP macros. To handle messages in an alternate map, just place the normal handler macros after an ALT_MSG_MAP (msgMapID) macro. Alternate message maps are primarily used in conjunction with CContainedWindow to enable a containing object to process messages for a contained window. CContainedWindow takes a message-map ID in the constructor and allows you to specify the message-map ID to change dynamically by using the SwitchMessageMap function.

Chaining Message Maps

Alternate message maps provide a way to group message handlers within a single BEGIN/END macro pair. However, additional message maps can reside in separate CMessageMap-derived classes. This mechanism is useful for grouping certain message-handling tasks into a reusable class. You can declare a class that tracks focus messages on any window, like this:

 template <class T> class CFocusLogger : public CMessageMap { public:     BEGIN_MSG_MAP(CFocusLogger)         MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)         MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)     END_MSG_MAP()     LRESULT OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam,         BOOL& bHandled)     {         T* pT = static_cast<T*>(this);         LogFocusChange(pT->m_hWnd, FALSE);         bHandled = FALSE;         return 1;     }     LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam,         BOOL& bHandled)     {         T* pT = static_cast<T*>(this);         LogFocusChange(pT->m_hWnd, TRUE);         bHandled = FALSE;         return 1;     }     void LogFocusChange(HWND hwnd, BOOL bGotFocus)     {         // Log to wherever     } }; 

The CFocusLogger class is used as a base class for your CWindowImpl-derived class, as shown here:

 class CMainFrame : public CWindowImpl<CmainFrame, ...>,     public CFocusLogger<CMainFrame> { public:     CMainFrame();     virtual ~CMainFrame(); DECLARE_WND_CLASS(NULL);     BEGIN_MSG_MAP(CMainFrame)         CHAIN_MSG_MAP(CFocusLogger)     END_MSG_MAP() }; 

The message map then routes messages to CFocusLogger by adding the CHAIN_MSG_MAP macro to the map. CHAIN_MSG_MAP calls ProcessWindowMessage on a CMessageMap-derived base class. CHAIN_MSG_MAP will only route messages to a CMessageMap-derived class that your class inherits from. ATL provides other chaining macros for cases in which messages need to be routed to CMessageMap-derived objects contained in your class as data members. There are also macros to route messages to alternate message maps in base classes or contained members. Each chaining macro and its purpose is described in the following list:

  • CHAIN_MSG_MAP(theChainClass) Routes messages to the default message map within a CMessageMap-derived base class.
  • CHAIN_MSG_MAP_ALT(theChainClass, msgMapID) Routes messages to the alternate message map within a CMessageMap-derived base class.
  • CHAIN_MSG_MAP_MEMBER(theChainMember) Routes messages to the default message map within a data member of your class. (The data member must be a class type that derives from CMessageMap.)
  • CHAIN_MSG_MAP_ALT_MEMBER(theChainMember, msgMapID) Routes messages to an alternate message map in a data member of your class. (The data member must be a class type that derives from CMessageMap.)
  • CHAIN_MSG_MAP_DYNAMIC(dynaChainID) Enables you to specify a message-handler object at run time. Your class must derive from CDynamicChain, which maintains a map of CMessageMap objects to dynaChainID numbers. You can add or remove message-handler objects by using CDynamicChain::SetChainEntry and RemoveChainEntry. The default message map in the message-handler object is always used. There is no corresponding alternate dynamic-chaining macro.


Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net