CWindowImpl


The Window Class

The CWindowImpl class derives ultimately from CWindow and provides two additional features, window class registration and message handling. We discuss message handling after we explore how CWindowImpl manages the window class. First, notice that, unlike CWindow, the CWindowImpl member function Create doesn't take the name of a window class:

template <class T, class TBase = CWindow,                                  class TWinTraits = CControlWinTraits>                  class ATL_NO_VTABLE CWindowImpl                                      : public CWindowImplBaseT< TBase, TWinTraits > {             public:                                                              DECLARE_WND_CLASS(NULL)                                          HWND Create(HWND hWndParent, _U_RECT rect = NULL,                    LPCTSTR szWindowName = NULL,                                     DWORD dwStyle = 0, DWORD dwExStyle = 0,                          _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL); ...                                                              };                                                               


Instead of passing the name of the window class, the name of the window class is provided in the DECLARE_WND_CLASS macro. A value of NULL causes ATL to generate a window class of the form: ATL<8-digit number>. We could declare a CWindowImpl-based class using the same window class name we registered using RegisterClass. However, that's not necessary. It's far more convenient to let ATL register the window class the first time we call Create on an instance of our CWindowImpl-derived class. This initial window class registration is done in the implementation of CWindowImpl::Create:

HWND CWindowImpl::Create(HWND hWndParent, _U_RECT rect = NULL,            LPCTSTR szWindowName = NULL,                                      DWORD dwStyle = 0, DWORD dwExStyle = 0,                           _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) {   // Generate a class name if one is not provided   if (T::GetWndClassInfo().m_lpszOrigName == NULL)                    T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();        // Register the window class if it hasn't   // already been registered   ATOM atom = T::GetWndClassInfo().Register(                          &m_pfnSuperWindowProc);                                         ...                                                             }                                                                 


Using a class derived from CWindowImpl, our program has gotten much smaller. We can eliminate the InitInstance function and place the main window creation directly in the _tWinMain function:

class CMainWindow : public CWindowImpl<CMainWindow> {...}; // Entry point int APIENTRY _tWinMain(HINSTANCE hinst,   HINSTANCE /*hinstPrev*/,   LPTSTR    pszCmdLine,   int       nCmdShow) {   // Initialize global strings   LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);   CMainWindow wnd;   wnd.Create( 0, CWindow::rcDefault, szTitle,     WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE );   if( !wnd ) {     return FALSE;   }   wnd.CenterWindow( );   wnd.ShowWindow( nCmdShow );   wnd.UpdateWindow( );   // Show the main window, run the message loop   ...   return msg.wParam; } 


We can also eliminate the generated WndProc; the CWindowImpl class provides message maps to handle message processing, as discussed later in the section "Handling Messages."

Modifying the Window Class

Each CWindowImpl class maintains a static data structure called a CWndClassInfo, which is a type definition for either an _ATL_WNDCLASSINFOA or an _ATL_WNDCLASSINFOW structure, depending on whether you're doing a Unicode build. The Unicode version is shown here:

struct _ATL_WNDCLASSINFOW {                                     WNDCLASSEXW m_wc;                                           LPCWSTR m_lpszOrigName;                                     WNDPROC pWndProc;                                           LPCWSTR m_lpszCursorID;                                     BOOL m_bSystemCursor;                                       ATOM m_atom;                                                WCHAR m_szAutoName[5+sizeof(void)*CHAR_BIT];                ATOM Register(WNDPROC* p)                                   { return AtlModuleRegisterWndClassInfoW(&_AtlWinModule,         &_AtlBaseModule, this, p); }                        };                                                          


The most important members of this structure are m_wc and m_atom. The m_wc member represents the window class structurethat is, what you would use to register a class if you were doing it by hand. The m_atom is used to determine whether the class has already been registered. This is useful if you want to make changes to the m_wc before the class has been registered.

Each class derived from CWindowImpl gets an instance of CWndClassInfo in the base class from the use of the DECLARE_WND_CLASS macro, defined like so:

#define DECLARE_WND_CLASS(WndClassName) \                         static CWndClassInfo& GetWndClassInfo() { \                         static CWndClassInfo wc = { \                                       { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, \       StartWindowProc, 0, 0, NULL, NULL, NULL, \                        (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL \          }, \                                                              NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \                        }; \                                                              return wc; \                                                    }                                                                 


This macro defines a function called GetWndClassInfo and initializes the values to commonly used defaults. If you want to also specify the class style and the background color, you can use another macro, called DECLARE_WND_CLASS_EX:

#define DECLARE_WND_CLASS_EX(WndClassName, style, bkgnd) \ static CWndClassInfo& GetWndClassInfo() { \                  static CWndClassInfo wc = { \                                { sizeof(WNDCLASSEX), style, StartWindowProc, \              0, 0, NULL, NULL, NULL, (HBRUSH)(bkgnd + 1), NULL, \       WndClassName, NULL \                                     }, \                                                       NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \                 }; \                                                       return wc; \                                             }                                                          


However, neither macro provides enough flexibly to set all the window class information you want, such as large and small icons, the cursor, or the background brush. Although it's possible to define an entire set of macros in the same vein as DECLARE_WND_CLASS, the combinations of what you want to set and what you want to leave as a default value quickly get out of hand. Frankly, it's easier to modify the CWndClassInfo structure directly using the GetWndClassInfo function. The CWindowImpl-derived class's constructor is a good place to do that, using the m_atom variable to determine whether the window class has already been registered:

CMainWindow( ) {     CWndClassInfo& wci = GetWndClassInfo();     if( !wci.m_atom ) {         wci.m_wc.lpszMenuName = MAKEINTRESOURCE(IDC_ATLHELLOWIN);         wci.m_wc.hIcon = LoadIcon(             _AtlBaseModule.GetResourceInstance(),             MAKEINTRESOURCE(IDI_ATLHELLOWIN));         wci.m_wc.hIconSm = LoadIcon(             _AtlBaseModule.GetResourceInstance(),             MAKEINTRESOURCE(IDI_SMALL));         wci.m_wc.hbrBackground = CreateHatchBrush(HS_DIAGCROSS,             RGB(0, 0, 255));     } } 


Setting the WNDCLASSEX member directly works for most of the members of the m_wc member of CWndClassInfo. However, the ATL team decided to treat cursors differently. For cursors, the CWndClassInfo structure has two members, m_lpszCursorID and m_bSystemCursor, which are used to override whatever is set in the hCursor member of m_wc. For example, to set a cursor from the available system cursors, you must do the following:

// Can't do this: // wci.m_wc.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_CROSS)); // Must do this: wci.m_bSystemCursor = TRUE; wci.m_lpszCursorID = IDC_CROSS; 


Likewise, to load a custom cursor, the following is required:

// Can't do this: // wci.m_wc.hCursor = LoadCursor( //   _AtlBaseModule.GetResourceInstance(), //   MAKEINTRESOURCE(IDC_BAREBONES)); // Must do this: wci.m_bSystemCursor = FALSE; wci.m_lpszCursorID = MAKEINTRESOURCE(IDC_BAREBONES); 


Remember to keep this special treatment of cursors in mind when creating CWindowImpl-derived classes with custom cursors.

Window Traits

In the same way that an icon and a cursor are coupled with a window class, often it makes sense for the styles and the extended styles to be coupled as well. For example, frame windows have different styles than child windows. When we develop a window class, we typically know how it will be used; for example, our CMainWindow class will be used as a frame window, and WS_OVERLAPPEDWINDOW will be part of the styles for every instance of CMainWindow. Unfortunately, there is no way to set default styles for a window class in the Win32 API. Instead, the window styles must be specified in every call to CreateWindowEx. To allow default styles and extended styles to be coupled with a window class, ATL enables you to group styles and reuse them in an instance of the CWinTrait class:

template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0>  class CWinTraits {                                     public:                                                  static DWORD GetWndStyle(DWORD dwStyle)                { return dwStyle == 0 ? t_dwStyle : dwStyle; }         static DWORD GetWndExStyle(DWORD dwExStyle)            { return dwExStyle == 0 ? t_dwExStyle : dwExStyle; } };                                                     


As you can see, the CWinTrait class holds a set of styles and extended styles. When combined with a style or an extended style DWORD, it hands out the passed DWORD if it is nonzero; otherwise, it hands out its own value. For example, to bundle my preferred styles into a Windows trait, I would do the following:

typedef CWinTraits<WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE>   CMainWinTraits; 


A Window traits class can be associated with a CWindowImpl by passing it as a template parameter:

class CMainWindow : public CWindowImpl<CMainWindow,   CWindow, CMainWinTraits> {...}; 


Now when creating instances of a CWindowImpl-derived class, I can be explicit about what parameters I want. Or, by passing zero for the style or the extended style, I can get the Window traits style associated with the class:

// Use the default value of 0 for the style and the // extended style to get the window traits for this class. wnd.Create(NULL, CWindow::rcDefault, __T("Windows Application")); 


Because I've used a CWinTrait class to group related styles and extended styles, if I need to change a style in a trait, the change is propagated to all instances of any class that uses that trait. This saves me from finding the instances and manually changing them one at a time. For the three most common kinds of windowsframe windows, child windows, and MDI child windowsATL comes with three built-in window traits classes:

typedef                                              CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |   WS_CLIPSIBLINGS, 0>                                CControlWinTraits;                                   typedef                                              CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |     WS_CLIPSIBLINGS,                                     WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>                CFrameWinTraits;                                     typedef                                              CWinTraits<WS_OVERLAPPEDWINDOW | WS_CHILD |            WS_VISIBLE | WS_CLIPCHILDREN |                       WS_CLIPSIBLINGS,                                     WS_EX_MDICHILD>                                    CMDIChildWinTraits;                                  


If you want to leverage the styles of an existing window traits class but add styles, you can use the CWindowTraitsOR class:

template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0,                              class TWinTraits = CControlWinTraits>                          class CWinTraitsOR {                                                     public:                                                                    static DWORD GetWndStyle(DWORD dwStyle) {                                  return dwStyle | t_dwStyle |                                               TWinTraits::GetWndStyle(dwStyle);                                    }                                                                        static DWORD GetWndExStyle(DWORD dwExStyle) {                            return dwExStyle | t_dwExStyle | TWinTraits::GetWndExStyle(dwExStyle);   }                                                                      };                                                                       


Using CWinTraitsOR, CMainWinTraits can be redefined like so:

// Leave CFrameWinTraits styles alone. // Add the WS_EX_CLIENTEDGE bit to the extended styles. typedef CWinTraitsOR<0, WS_EX_CLIENTEDGE, CFrameWinTraits>   CMainWinTraits; 


The Window Procedure

To handle window messages, every window needs a window procedure (WndProc). This WndProc is set in the lpfnWndProc member of the WNDCLASSEX structure used during window registration. You might have noticed that in the expansion of DECLARE_WND_CLASS and DECLARE_WND_CLASS_EX, the name of the windows procedure is StartWindowProc. StartWindowProc is a static member function of CWindowImplBase. Its job is to establish the mapping between the CWindowImpl-derived object's HWND and the object's this pointer. The goal is to handle calls made by Windows to a WndProc global function and map them to a member function on an object. The mapping between HWND and an object's this pointer is done by the StartWindowProc when handling the first window message.[1] After the new HWND is cached in the CWindowImpl-derived object's member data, the object's real window procedure is substituted for the StartWindowProc, as shown here:

[1] The first window message is the moment when Windows first communicates the new HWND to the application. This happens before CreateWindow[Ex] returns with the HWND.

template <class TBase, class TWinTraits>                 LRESULT CALLBACK                                         CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(      HWND hWnd,                                               UINT uMsg,                                               WPARAM wParam, LPARAM lParam)                        {                                                            CWindowImplBaseT< TBase, TWinTraits >* pThis =               (CWindowImplBaseT< TBase, TWinTraits >*)                 _AtlWinModule.ExtractCreateWndData();                ATLASSERT(pThis != NULL);                                pThis->m_hWnd = hWnd;                                    pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);      WNDPROC pProc = pThis->m_thunk.GetWNDPROC();             WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd,         GWLP_WNDPROC, (LONG_PTR)pProc);                      return pProc(hWnd, uMsg, wParam, lParam);            }                                                        


The m_thunk member is the interesting part. The ATL team had several different options it could have used to map the HWND associated with each incoming window message to the object's this pointer responsible for handling the message. It could have kept a global table that mapped HWNDs to this pointers, but the look-up time would grow as the number of windows grew. It could have tucked the this pointer into the window data,[2] but then the application/component developer could unwittingly overwrite the data when doing work with window data. Plus, empirically, this look-up is not as fast as one might like when handling many messages per second.

[2] A window can maintain extra information via the cbWndExtra member of the WNDCLASS[EX] structure or via the GetWindowLong/SetWindowLong family of functions.

Instead, the ATL team used a technique based on a set of Assembly (ASM) instructions grouped together into a thunk, avoiding any look-up. The term thunk is overused in Windows, but in this case, the thunk is a group of ASM instructions that keeps track of a CWindowImpl object's this pointer and acts like a functionspecifically, a WndProc. Each CWindowImpl object gets its own thunkthat is, each object has its own WndProc. A thunk is a set of machine instructions built on-the-fly and executed. For example, imagine two windows of the same class created like this:

class CMyWindow : public CWindowImpl<CMyWindow> {...}; CMyWindow wnd1; wnd1.Create(...); CMyWindow wnd2; wnd2.Create(...); 


Figure 10.3 shows the per-window class and per-HWND data maintained by Windows on the 32-bit Intel platform, the thunks, and the CWindowImpl objects that would result from this example code.

Figure 10.3. One thunk per CWindowImpl object


The thunk's job is to replace the HWND on the stack with the CWindowImpl object's this pointer before calling the CWindowImpl static member function WindowProc to further process the message. The ASM instructions that replace the HWND with the object's this pointer are kept in a data structure called the _stdcallthunk. Versions of this structure are defined for 32-bit x86, AMD64, ALPHA, MIPS, SH3, ARM, and IA64 (Itanium) processors. The 32-bit x86 definition follows:

#if defined(_M_IX86)                                     PVOID __stdcall __AllocStdCallThunk(VOID);               VOID  __stdcall __FreeStdCallThunk(PVOID);               #pragma pack(push,1)                                     struct _stdcallthunk {                                       DWORD   m_mov;     // mov dword ptr [esp+0x4], pThis                        // (esp+0x4 is hWnd)                  DWORD   m_this;    // Our CWindowImpl this pointer       BYTE    m_jmp;     // jmp WndProc                        DWORD   m_relproc; // relative jmp                       BOOL Init(DWORD_PTR proc, void* pThis) {                     m_mov = 0x042444C7; //C7 44 24 0C                        m_this = PtrToUlong(pThis);                              m_jmp = 0xe9;                                            m_relproc = DWORD((INT_PTR)proc                             ((INT_PTR)this+sizeof(_stdcallthunk)));              // write block from data cache and                       //  flush from instruction cache                         FlushInstructionCache(GetCurrentProcess(), this,             sizeof(_stdcallthunk));                              return TRUE;                                         }                                                        // some thunks will dynamically allocate the             // memory for the code                                   void* GetCodeAddress() {                                     return this;                                         }                                                        void* operator new(size_t) {                                 return __AllocStdCallThunk();                        }                                                        void operator delete(void* pThunk) {                         __FreeStdCallThunk(pThunk);                          }                                                    };                                                       #pragma pack(pop)                                        #elif defined(_M_AMD64)                                  ... // Other processors omitted for clarity              


As you can see, this structure initializes itself with the appropriate machine code to implement the per-window code needed to feed the right this pointer into the window proc. The overload of operator new is needed to deal with the no-execute protection feature on newer processors. The call to __AllocStdCallThunk ensures that the thunk is allocated on a virtual memory page that has execute permissions instead of on the regular heap, which might not allow code execution.

This data structure is kept per CWindowImpl-derived object and is initialized by StartWindowProc with the object's this pointer and the address of the static member function used as the window procedure. The m_thunk member that StartWindowProc initializes and uses as the thunking window procedure as an instance of the CWndProcThunk class follows:

class CwndProcThunk {                              public:                                                _AtlCreateWndData cd;                              CStdCallThunk thunk;                               BOOL Init(WNDPROC proc, void* pThis) {                 return thunk.Init((DWORD_PTR)proc, pThis);     }                                                  WNDPROC GetWNDPROC() {                                 return (WNDPROC)thunk.GetCodeAddress();        }                                              };                                                 


After the thunk has been set up in StartWindowProc, each window message is routed from the CWindowImpl object's thunk to a static member function of CWindowImpl, to a member function of the CWindowImpl object itself, shown in Figure 10.4.

Figure 10.4. Each message is routed through a thunk to map the HWND to the this pointer.


On each Window message, the thunk removes the HWND provided by Windows as the first argument and replaces it with the CWindowImpl-derived object's this pointer. The thunk then forwards the entire call stack to the actual window procedure. Unless the virtual GetWindowProc function is overridden, the default window procedure is the WindowProc static function shown here:

template <class TBase, class TWinTraits>                           LRESULT CALLBACK                                                   CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd,           UINT uMsg, WPARAM wParam, LPARAM lParam) {                         CWindowImplBaseT< TBase, TWinTraits >* pThis =                         (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;                  // set a ptr to this message and save the old value                _ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam);                 const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg;                    pThis->m_pCurrentMsg = &msg;                                       // pass to the message map to process                              LRESULT lRes;                                                      BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd,                 uMsg, wParam, lParam, lRes, 0);                                // restore saved value for the current message                     ATLASSERT(pThis->m_pCurrentMsg == &msg);                           // do the default processing if message was not handled            if(!bRet) {                                                            if(uMsg != WM_NCDESTROY)                                               lRes = pThis->DefWindowProc(uMsg, wParam, lParam);             else {                                                                 // unsubclass, if needed                                           LONG_PTR pfnWndProc = ::GetWindowLongPtr(                              pThis->m_hWnd, GWLP_WNDPROC);                                  lRes = pThis->DefWindowProc(uMsg, wParam, lParam);                 if(pThis->m_pfnSuperWindowProc != ::DefWindowProc &&                   ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC)                    == pfnWndProc)                                                     ::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC,                        (LONG_PTR)pThis->m_pfnSuperWindowProc);                    // mark window as destroyed                                        pThis->m_dwState |= WINSTATE_DESTROYED;                       }                                                              }                                                                  if((pThis->m_dwState & WINSTATE_DESTROYED) && pOldMsg== NULL) {        // clear out window handle                                         HWND hWndThis = pThis->m_hWnd;                                     pThis->m_hWnd = NULL;                                              pThis->m_dwState &= ~WINSTATE_DESTROYED;                           // clean up after window is destroyed                              pThis->m_pCurrentMsg = pOldMsg;                                    pThis->OnFinalMessage(hWndThis);                               }                                                                  else {                                                                 pThis->m_pCurrentMsg = pOldMsg;                                }                                                                  return lRes;                                                    }                                                                  


The first thing WindowProc does is extract the object's this pointer from the call stack by casting the HWND parameter. Because this HWND parameter has been cached in the object's m_hWnd member, no information is lost. However, if you override GetWindowProc and provide a custom WndProc for use by the window thunk, remember that the HWND is a this pointer, not an HWND.

After obtaining the object's this pointer, WindowProc caches the current message into the m_pCurrentMsg member of the CWindowImpl-derived object. This message is then passed along to the CWindowImpl-derived object's virtual member function ProcessWindowMessage, which must be provided by the deriving class with the following signature:

virtual BOOL                               ProcessWindowMessage(HWND hWnd, UINT uMsg,   WPARAM wParam, LPARAM lParam,              LRESULT& lResult, DWORD dwMsgMapID);     


This is where any message handling is to be done by the object. For example, our CMainWindow could handle Windows messages like this:

class CMainWindow : public CWindowImpl<CMainWindow> { public:   virtual BOOL   ProcessWindowMessage(HWND hWnd, UINT uMsg,     WPARAM wParam, LPARAM lParam,     LRESULT& lResult, DWORD /*dwMsgMapID*/) {     BOOL bHandled = TRUE;     switch( uMsg ) {     case WM_PAINT:   lResult = OnPaint(); break;     case WM_DESTROY: lResult = OnDestroy(); break;     default:         bHandled = FALSE; break;     }     return bHandled;   } private:   LRESULT OnPaint(); {     PAINTSTRUCT ps;     HDC         hdc = BeginPaint(&ps);     RECT        rect; GetClientRect(&rect);     DrawText(hdc, __T("Hello, Windows"), -1, &rect,              DT_CENTER | DT_VCENTER | DT_SINGLELINE);     EndPaint(&ps);     return 0;   }   LRESULT OnDestroy() {     PostQuitMessage(0);     return 0;   } }; 


Notice that the message handlers are now member functions instead of global functions. This makes programming a bit more convenient. For example, inside the OnPaint handler, BeginPaint, GetClientRect, and EndPaint all resolve to member functions of the CMainWindow object to which the message has been sent. Also notice that returning FALSE from ProcessWindowMessage is all that is required if the window message is not handled. WindowProc handles calling DefWindowProc for unhandled messages.

As a further convenience, WindowProc calls OnFinalMessage after the window handles the last message and after the HWND has been zeroed out. This is handy for shutdown when used on the application's main window. For example, we can remove WM_DESTROY from our switch statement and replace the OnDestroy handler with OnFinalMessage:

virtual void CMainWindow::OnFinalMessage(HWND /*hwnd*/) { PostQuitMessage(0); } 


As you might imagine, writing the ProcessWindowMessage function involves a lot of boilerplate coding, tediously mapping window messages to function names. I show you the message map that will handle this chore for us in the later section, "Handling Messages."

Window Superclassing

The Windows object model of declaring a window class and creating instances of that class is similar to that of the C++ object model. The WNDCLASSEX structure is to an HWND as a C++ class declaration is to a this pointer. Extending this analogy, Windows superclassing[3] is like C++ inheritance. Superclassing is a technique in which the WNDCLASSEX structure for an existing window class is duplicated and given its own name and its own WndProc. When a message is received for that window, it's routed to the new WndProc. If that WndProc decides not the handle that message fully, instead of being routed to DefWindowProc, the message is routed to the original WndProc. If you think of the original WndProc as a virtual function, the superclassing window overrides the WndProc and decides on a message-by-message basis whether to let the base class handle the message.

[3] The theory of Windows superclassing is beyond the scope of this book. For a more in-depth discussion, see Win32 Programming (Addison-Wesley, 1997), by Brent Rector and Joe Newcomer.

The reason to use superclassing is the same reason to use inheritance of implementation: The base class has some functionality that the deriving class wants to extend. ATL supports superclassing via the DECLARE_WND_SUPERCLASS macro:

#define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) \ static ATL::CWndClassInfo& GetWndClassInfo() \                   { \                                                                 static ATL::CWndClassInfo wc = \                                 { \                                                                  { sizeof(WNDCLASSEX), 0, StartWindowProc, \                        0, 0, NULL, NULL, NULL, NULL, NULL, \                            WndClassName, NULL }, \                                        OrigWndClassName, NULL, NULL, TRUE, 0, _T("") \              }; \                                                             return wc; \                                                  }                                                                


The WndClassName is the name of the deriving class's window class. As with DECLARE_WND_CLASS[_EX], to have ATL generate a name, use NULL for this parameter. The OrigWndClassName parameter is the name of the existing window class you want to "inherit" from.

For example, the existing edit control provides the ES_NUMBER style to indicate that it should allow the input of only numbers. If you want to provide similar functionality but allow the input of only letters, you have two choices. First, you can build your own edit control from scratch. Second, you can superclass the existing edit control and handle WM_CHAR messages like this:

// Letters-only edit control class CLetterBox : public CWindowImpl<CLetterBox> { public:   DECLARE_WND_SUPERCLASS(0, "EDIT")   virtual BOOL   ProcessWindowMessage(HWND hWnd, UINT uMsg,     WPARAM wParam, LPARAM lParam,     LRESULT& lResult, DWORD /*dwMsgMapID*/) {     BOOL bHandled = TRUE;     switch( uMsg ) {     case WM_CHAR:       lResult = OnChar((TCHAR)wParam, bHandled); break;     default:       bHandled = FALSE; break;     }     return bHandled;   } private:   LRESULT OnChar(TCHAR c, BOOL& bHandled) {     if( isalpha(c) ) bHandled = FALSE;     else             MessageBeep(0xFFFFFFFF);     return 0;   } }; 


When an instance of CLetterBox is created, it looks and acts just like a built-in Windows edit control, except that it accepts only letters, beeping otherwise.

Although superclassing is powerful, it turns out that it is rarely used. Superclassing is useful when you need to create more than one instance of a derived window type. More often in Win32 development, you instead want to customize a single window. A much more commonly used technique is known as subclassing, which I discuss later when I present CContainedWindow in the section titled (surprisingly enough) "CContainedWindow."

Handling Messages

Whether it's superclassing, subclassing, or neither, a major part of registering a Window class is providing the WndProc. The WndProc determines the behavior of the window by handling the appropriate messages. You've seen how the default WindowProc that ATL provides routes the messages to the ProcessWindowMessage function that your CWindowImpl-derived class provides. You've also seen how tedious it is to route messages from the ProcessWindowMessage function to the individual message-handler member functions. Toward that end, ATL provides a set of macros for building a message map that will generate an implementation of ProcessWindowMessage for you. Providing the skeleton of the message map are the BEGIN_MSG_MAP and END_MSG_MAP macros, defined like this:

#define BEGIN_MSG_MAP(theClass) \                     public: \                                               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:                                           #define END_MSG_MAP() \                                     break; \                                            default: \                                            ATLTRACE(ATL::atlTraceWindowing, 0, \                   _T("Invalid message map ID (%i)\n"), \                dwMsgMapID); \                                      ATLASSERT(FALSE); \                                   break; \                                            } \                                                   return FALSE; \                                     }                                                     


Notice that the message map is a giant switch statement. However, the switch is not on the message IDs themselves, but rather on the message map ID. A single set of message map macros can handle the messages of several windows, typically the parent and several children. The parent window, the window for which we're providing the message map, is identified with the message map ID of 0. Later I discuss segregating message handling into different sections of the same message map, resulting in nonzero message map IDs.

Handling General Messages

Each message that the window wants to handle corresponds to an entry in the message map. The simplest is the MESSAGE_HANDLER macro, which provides a handler for a single message:

#define MESSAGE_HANDLER(msg, func) \                    if(uMsg == msg) { \                                     bHandled = TRUE; \                                    lResult = func(uMsg, wParam, lParam, bHandled); \     if(bHandled) return TRUE; \                         }                                                   


If you want to use a single message handler for a range of Windows messages, you can use the MESSAGE_RANGE_HANDLER macro:

#define MESSAGE_RANGE_HANDLER(msgFirst, msgLast, func) \   if(uMsg >= msgFirst && uMsg <= msgLast) { \                bHandled = TRUE; \                                       lResult = func(uMsg, wParam, lParam, bHandled); \        if(bHandled) return TRUE; \                            }                                                      


Using the message map macros, we can replace the sample implementation of ProcessWindowMessage with the following message map:

BEGIN_MSG_MAP(CMainWindow)   MESSAGE_HANDLER(WM_PAINT, OnPaint) END_MSG_MAP() 


This expands roughly to the following implementation of ProcessWindow-Message:

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


Note two points here. First, if there is an entry in the message map, that message is assumed to be handledthat is, the default window procedure is not called for that message. However, the BOOL& bHandled is provided to each message handler, so you can change it to FALSE in the message handler if you want ATL to keep looking for a handler for this message. If you are subclassing or superclassing, the original window procedure receives the message only when bHandled is set to FALSE. Also, it's possible that another message handler further down the map will receive the message. This is useful for message map chaining, which I discuss in the section "Message Chaining." Ultimately, if nobody is interested in the message, DefWindowProc gets it.

The second interesting note in this generated code is the member function signature required to handle a message map entry. All general messages are passed to a message handler with the following signature:

LRESULT                                                 MessageHandler(UINT nMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled);                                        


You can either add the entry and the member function by hand, or, in Class view, you can right-click any class with a message map, choose Properties, and click the Messages button at the top of the property window. This gives you a list of the messages that you can handle, and, if you enter a function name in one of the slots, Visual Studio adds a MessageHandler macro to the map, a declaration to the .h file, and a stub to the .cpp file (as described in Chapter 1, ""Hello, ATL). Unfortunately, however you add the handler, you're still responsible for cracking your own messages. When you crack a message, you pull the data appropriate for the specific message from the wParam and lParam arguments passed to the message-handler method. For example, if you want to extract the coordinates of a mouse click, you must manually unpack the x and y positions from the lParam argument:

LRESULT CMainWindow::OnLButtonDown(UINT nMsg, WPARAM wParam,     LPARAM lParam, BOOL &bHandled) {     int xPos = GET_X_LPARAM(lParam);     int yPos = GET_Y_LPARAM(lParam);     ...     return 0; } 


At last count, there were more than 300 standard messages, each with their own interpretation of WPARAM and LPARAM, so this can be quite a job. Luckily, the Windows Template Library (WTL)[4] add-on library to ATL, mentioned later in this chapter, provides this and more.

[4] Available at http://wtl.sourceforge.net.

WM_COMMAND and WM_NOTIFY Messages

Of the hundreds of Windows messages, ATL provides a bit of message-cracking assistance for two of them, WM_COMMAND and WM_NOTIFY. These messages represent how a Windows control communicates with its parent. I should point out that Windows controls are not OLE or ActiveX controls. A standard Windows control is a child window whose class is defined by the Windows operating system. Some of these controls have been with us since Windows 1.0. Classic examples of Windows control include buttons, scrollbars, edit boxes, list boxes, and combo boxes. With the new Windows shell introduced with Windows 95, these controls were expanded to include toolbars, status bars, tree views, list views, rich-text edit boxes, and more. Furthermore, with the integration of Internet Explorer with the shell, more Windows controls were introduced to include rebars, the date picker, and the IP address control, for example.

Creating a Windows control is a matter of calling CreateWindow with the proper window class namefor example, EDITjust like creating any other kind of window. Communicating from the parent to a child Windows controls is a matter of calling SendMessage with the appropriate parameters, as in EM_GETSEL to get the currently selected text in a child EDIT control. Communicating from a child window to its parent also works via SendMessage, most often using the WM_COMMAND or WM_NOTIFY messages. These messages provide enough information packed into WPARAM and LPARAM to describe the event of which the control is notifying the parent, as shown here:

WM_COMMAND   wNotifyCode = HIWORD(wParam); // notification code   wID = LOWORD(wParam);         // item, control, or                                 // accelerator identifier   hwndCtl = (HWND)lParam;       // handle of control WM_NOTIFY   idCtrl = (int)wParam;         // control identifier   pnmh = (LPNMHDR)lParam;       // address of NMHDR structure 


Notice that the WM_NOTIFY message is accompanied by a pointer to a NMHDR, which is defined like this:

typedef struct tagNMHDR {   HWND hwndFrom; // handle of the control   UINT idFrom;   // control identifier   UINT code;     // notification code } NMHDR; 


For example, an edit box notifies the parent of a change in the text with a WM_COMMAND message using the EN_CHANGE notification code. The parent might or might not want to handle this particular message. If it does, it wants to avoid the responsibility of breaking out the individual parts of the command notification. ATL provides several macros for splitting the parts of WM_COMMAND and WM_NOTIFY messages. All these macros assume the following handler-function signatures:

LRESULT                                                  CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl,   BOOL& bHandled);                                       LRESULT                                                  NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled); 


The most basic handler macros are COMMAND_HANDLER and NOTIFY_HANDLER:

#define COMMAND_HANDLER(id, code, func) \                       if(uMsg == WM_COMMAND && \                                       id == LOWORD(wParam) && \                                     code == HIWORD(wParam)) \                                  { \                                                             bHandled = TRUE; \                                            lResult = func(HIWORD(wParam), LOWORD(wParam), \                (HWND)lParam, bHandled); \                                  if(bHandled) return TRUE; \                                 }                                                           #define NOTIFY_HANDLER(id, code, func) \                        if(uMsg == WM_NOTIFY && \                                        id == ((LPNMHDR)lParam)->idFrom && \                          code == ((LPNMHDR)lParam)->code) \                         { \                                                             bHandled = TRUE; \                                            lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \     if(bHandled) return TRUE; \                                 }                                                           


These basic handler macros let you specify both the id of the control and the command/notification code that the control is sending. Using these macros, handling an EN_CHANGE notification from an edit control looks like this:

COMMAND_HANDLER(IDC_EDIT1, EN_CHANGE, OnEdit1Change) 


Likewise, handling a TBN_BEGINDRAG notification from a toolbar control looks like this:

NOTIFY_HANDLER(IDC_TOOLBAR1, TBN_BEGINDRAG, OnToolbar1BeginDrag) 


As an example, let's add a menu bar to the sample Windows application:

int APIENTRY _tWinMain(HINSTANCE hinst,                        HINSTANCE /*hinstPrev*/,                        LPTSTR    pszCmdLine,                        int       nCmdShow) {   // Initialize the ATL module   ...   // Create the main window   CMainWindow wnd;   // Load a menu   HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),                          MAKEINTRESOURCE(IDR_MENU));   // Use the value 0 for the style and the extended style   // to get the window traits for this class.   wnd.Create(0, CWindow::rcDefault, __T("Windows Application"),     0, 0, (UINT)hMenu);   if( !wnd ) return -1;   ... // The rest is the same } 


Assuming standard File, Exit and Help, About items in our menu, handling the menu item selections looks like this:

class CMainWindow : public CWindowImpl<CMainWindow,   CWindow, CMainWinTraits> { public: BEGIN_MSG_MAP(CMainWindow)   MESSAGE_HANDLER(WM_PAINT, OnPaint)   COMMAND_HANDLER(ID_FILE_EXIT, 0, OnFileExit)   COMMAND_HANDLER(ID_HELP_ABOUT, 0, OnHelpAbout) END_MSG_MAP() ...   LRESULT OnFileExit(WORD wNotifyCode, WORD wID, HWND hWndCtl,                      BOOL& bHandled);   LRESULT OnHelpAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl,                       BOOL& bHandled); }; 


You might notice that menus are a little different from most Windows controls. Instead of using the ID of a child window as the first parameter, such as for an edit control, we use the ID of the menu item; the command code itself is unused. In general, it's not uncommon for the ID or the code to be unimportant when routing a message to a handler. You've already seen one example: Handling a menu item doesn't require checking the code. Another example of when you don't need to worry about the code is if you want to route all events for one control to a single handler. Because the code is provided as an argument to the handler, further decisions can be made about how to handle a specific code for a control. To route events without regard for the specific code, ATL provides COMMAND_ID_HANDLER and NOTIFY_ID_HANDLER:

#define COMMAND_ID_HANDLER(id, func) \                          if(uMsg == WM_COMMAND && id == LOWORD(wParam)) \              { \                                                                 bHandled = TRUE; \                                        lResult = func(HIWORD(wParam), LOWORD(wParam), \                (HWND)lParam, bHandled); \                                  if(bHandled) \                                                  return TRUE; \                                            }                                                           #define NOTIFY_ID_HANDLER(id, func) \                           if(uMsg == WM_NOTIFY && id == ((LPNMHDR)lParam)->idFrom) \    { \                                                                 bHandled = TRUE; \                                        lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \     if(bHandled) \                                                return TRUE; \                                              }                                                           


Using COMMAND_ID_HANDLER, our menu routing would more conventionally be written this way:

COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout) 


Furthermore, if you want to route notifications for a range of controls, ATL provides COMMAND_RANGE_HANDLER and NOTIFY_RANGE_HANDLER:

#define COMMAND_RANGE_HANDLER(idFirst, idLast, func) \          if(uMsg == WM_COMMAND && LOWORD(wParam) >= idFirst && \         LOWORD(wParam) <= idLast) \                                 { \                                                                 bHandled = TRUE; \                                        lResult = func(HIWORD(wParam), LOWORD(wParam), \                (HWND)lParam, bHandled); \                                  if(bHandled) \                                                return TRUE; \                                              }                                                           #define NOTIFY_RANGE_HANDLER(idFirst, idLast, func) \           if(uMsg == WM_NOTIFY && \                                       ((LPNMHDR)lParam)->idFrom >= idFirst && \                     ((LPNMHDR)lParam)->idFrom <= idLast) \                      { \                                                                 bHandled = TRUE; \                                        lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \     if(bHandled) \                                                return TRUE; \                                              }                                                           


It's also possible that you want to route messages without regard for their ID. This is useful if you want to use a single handler for multiple controls. ATL supports this use with COMMAND_CODE_HANDLER and NOTIFY_CODE_HANDLER:

#define COMMAND_CODE_HANDLER(code, func) \                      if(uMsg == WM_COMMAND && code == HIWORD(wParam)) \            { \                                                             bHandled = TRUE; \                                            lResult = func(HIWORD(wParam), LOWORD(wParam), \                (HWND)lParam, bHandled); \                                  if(bHandled) \                                                return TRUE; \                                              }                                                           #define NOTIFY_CODE_HANDLER(cd, func) \                         if(uMsg == WM_NOTIFY && cd == ((LPNMHDR)lParam)->code) \      { \                                                             bHandled = TRUE; \                                            lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \     if(bHandled) \                                                return TRUE; \                                              }                                                           


Again, because the ID of the control is available as a parameter to the handler, you can make further decisions based on which control is sending the notification code.

Why WM_NOTIFY?

As an aside, you might be wondering why we have both WM_COMMAND and WM_NOTIFY. After all, WM_COMMAND alone sufficed for Windows 1.0 through Windows 3.x. However, when the new shell team was building the new controls, team members really wanted to send along more information than just the ID of the control and the notification code. Unfortunately, all the bits of both WPARAM and LPARAM were already being used in WM_COMMAND, so the shell team invented a new message so that they could send a pointer to a structure as the LPARAM, keeping the ID of the control in the WPARAM (as in WM_NOTIFY). However, if you examine the definition of the NMHDR structure, you'll notice that there is no more information than was available in WM_COMMAND. Actually, there is a difference. Depending on the type of control that is sending the message, the LPARAM could point to something else that has the same layout as an NMHDR but that has extra information tacked onto the end. For example, if you receive a TBN_BEGIN_DRAG, the NMHDR pointer actually points to an NMTOOLBAR structure:

typedef struct tagNMTOOLBAR {      NMHDR    hdr;                  int      iItem;                TBBUTTON tbButton;             int      cchText;              LPTSTR   pszText;          } NMTOOLBAR, FAR* LPNMTOOLBAR; 


Because the first member of the NMTOOLBAR structure is an NMDHR, it's safe to cast the LPARAM to an NMHDR, even though it actually points at an NMTOOLBAR. If you want, you can consider this "inheritance" for C programmers. . ..

Message Reflection and Forwarding

In many cases, Windows controls send messages to their parent windows: when a button is clicked, when a treeview item is expanded, or when a list-box item is selected, for example. These messages are usually the result of user action, and the parent window is often the best place to handle the user's request.

Other messages are also sent to the parent window. These messages are sent not as the result of a user's action, but as a way for the parent window to customize something about the control's operation. The classic example is the WM_CTLCOLORXXX set of messages, which are sent to the parent window to allow it to change the default colors when a control draws. Handling such messages is fairly easy. For example, imagine that I want to display a static window with red text on a green background:

class CMainWindow : public CWindowImpl< CMainWindow, CWindow,   CMainWinTraits > { public:     CMainWindow( ) {     }     BEGIN_MSG_MAP(CMainWindow)         MESSAGE_HANDLER(WM_CREATE, OnCreate)         MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnControlColorStatic)     END_MSG_MAP() private:     LRESULT OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam,         BOOL& bHandled) {         m_staticBackground = ::CreateSolidBrush(RGB(0, 255, 0));         RECT rc;         rc.left = 25;         rc.right = 300;         rc.top = 25;         rc.bottom = 75;         if( m_message.Create(_T("static"),             m_hWnd, rc, _T("Static Message"),             WS_VISIBLE | WS_CHILD ) ) {             return 0;         }         return -1; } LRESULT OnControlColorStatic(UINT nMsg,     WPARAM wParam, LPARAM lParam,     BOOL& bHandled) {     HDC hdc = reinterpret_cast< HDC >( wParam );     ::SetTextColor(hdc, RGB(255, 0, 0) );     ::SetBkColor( hdc, RGB(0, 255, 0 ) );     return reinterpret_cast< LRESULT >(m_staticBackground); } void OnFinalMessage(HWND hWnd) {     ::DeleteObject( m_staticBackground );     PostQuitMessage(0); }     CWindow m_message;     HBRUSH m_staticBackground; }; 


This code is fairly straightforward. At creation, we create a brush to hand to the static control when it paints. When we receive the WM_CTLCOLORSTATIC, we set up the provided HDC to draw in the proper colors. OnFinalMessage cleans up the brush we created so that we can be nice Windows citizens.

This implementation works well . . . until you add a second static control. Then, you need to figure out which control is drawing and set its colors appropriately in the OnControlColorStatic method. Or what if you want custom colors for a second window class? Now, we have to start copying and pasting. Ideally, we want to build a single CColoredStatic window class and let it handle all the details.

The only tricky part about writing that class is that the underlying static control is hard-wired to send the WM_CTLCOLORSTATIC message only to its parent window. Somehow in the implementation of CColoredStatic, we need to hook the message map of our parent window, preferably without having to warp the implementation of the parent window class to support our new child control.

The traditional Windows approach to this problem is to have your control subclass its parent window. This can be done, but it is a little tricky to get right. ATL has introduced a new feature called notification reflection that lets us easily get these parent notifications back to the original control.

In the parent message map, you need to add the REFLECT_NOTIFICATIONS() macro. This macro is defined as follows:

#define REFLECT_NOTIFICATIONS() \                  { \                                                bHandled = TRUE; \                             lResult = ReflectNotifications(uMsg, \             wParam, lParam, bHandled); \               if(bHandled) \                                     return TRUE; \                         }                                          


This passes the message on to the ReflectNotifications method, which is defined in CWindowImplRoot:

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;                                                        case WM_MEASUREITEM:                                                  if(wParam)    // not from a menu                                      hWndChild = GetDlgItem(                                               ((LPMEASUREITEMSTRUCT)lParam)->CtlID);                    break;                                                        case WM_COMPAREITEM:                                                  if(wParam)    // not from a menu                                      hWndChild = ((LPCOMPAREITEMSTRUCT)lParam)->hwndItem;          break;                                                        case WM_DELETEITEM:                                                   if(wParam)    // not from a menu                                      hWndChild = ((LPDELETEITEMSTRUCT)lParam)->hwndItem;           break;                                                        case WM_VKEYTOITEM:                                               case WM_CHARTOITEM:                                               case WM_HSCROLL:                                                  case WM_VSCROLL:                                                      hWndChild = (HWND)lParam;                                         break;                                                        case WM_CTLCOLORBTN:                                              case WM_CTLCOLORDLG:                                              case WM_CTLCOLOREDIT:                                             case WM_CTLCOLORLISTBOX:                                          case WM_CTLCOLORMSGBOX:                                           case WM_CTLCOLORSCROLLBAR:                                        case WM_CTLCOLORSTATIC:                                               hWndChild = (HWND)lParam;                                         break;                                                        default:                                                              break;                                                        }                                                                 if(hWndChild == NULL) {                                               bHandled = FALSE;                                                 return 1;                                                     }                                                                 ATLASSERT(::IsWindow(hWndChild));                                 return ::SendMessage(hWndChild, OCM__BASE + uMsg,                     wParam, lParam);                                          }                                                                 


This function simply looks for all the standard parent notifications and, if found, forwards the message back to the control that sent it. One thing to notice is that the message ID is changed by adding OCM_BASE to the message code. A set of macros is defined for forwarded messages that begin with OCM_ instead of WM_. The message values are changed so that the child control can tell that the message is a reflection back from its parent instead of a notification from one of its own children.

For our self-colorizing static control, we need to handle OCM_CTLCOLORSTATIC as follows:

class CColoredStatic : public CWindowImpl< CColoredStatic > { public:     DECLARE_WND_SUPERCLASS(0, _T("STATIC"))     CColoredStatic( COLORREF foreground, COLORREF background ) {         m_foreground = foreground;         m_background = background;         m_backgroundBrush = ::CreateSolidBrush( m_background );     }     ~CColoredStatic( ) {         ::DeleteObject( m_backgroundBrush );     } private:     BEGIN_MSG_MAP(CColoredStatic)         MESSAGE_HANDLER(OCM_CTLCOLORSTATIC, OnControlColorStatic)     END_MSG_MAP()     LRESULT OnControlColorStatic(UINT nMsg, WPARAM wParam,         LPARAM lParam, BOOL& bHandled) {         HDC hdc = reinterpret_cast< HDC >( wParam );         ::SetTextColor(hdc, m_foreground );         ::SetBkColor( hdc, m_background );         return reinterpret_cast< LRESULT >(m_backgroundBrush);     }     COLORREF m_foreground;     COLORREF m_background;     HBRUSH m_backgroundBrush; }; 


Our parent window class is simplified as well:

class CMainWindow :     public CWindowImpl< CMainWindow, CWindow, CMainWinTraits > { public:     CMainWindow( ) :         m_message( RGB( 0, 0, 255 ), RGB(255, 255, 255) ) {     }     BEGIN_MSG_MAP(CMainWindow)         MESSAGE_HANDLER(WM_CREATE, OnCreate)         REFLECT_NOTIFICATIONS()     END_MSG_MAP() ... }; 


A set of macros for use in the child control message map corresponds to the regular ones that handle command and notification messages: REFLECTED_COMMAND_HANDLER, REFLECTED_COMMAND_ID_HANDLER, REFLECTED_NOTIFY_HANDLER, and so on. In addition, DEFAULT_REFLECTION_HANDLER sends the reflected message down to DefWindowProc.

There's one minor nagging nit with the message-reflection setup: We still need to modify our parent window class. It sure would be nice to avoid this, especially when building reusable controls. Luckily, the ATL authors thought of this with the CWindowWithReflectorImpl class. Using this class is quite easy: Simply replace your CWindowImpl base class with CWindowWithReflectorImpl:

class CColoredStatic :     public CWindowWithReflectorImpl< CColoredStatic > { // ... the rest is exactly the same ... }; 


You can now remove the REFLECT_NOTIFICATIONS() macro from the parent's message map, and messages will still be routed as before.

The implementation of CWindowWithReflectorImpl is quite small:

template <class T, class TBase, class TwinTraits >                class ATL_NO_VTABLE CWindowWithReflectorImpl :                        public CWindowImpl< T, TBase, TWinTraits > {                  public:                                                               HWND Create(HWND hWndParent, _U_RECT rect = NULL,                     LPCTSTR szWindowName = NULL,                                      DWORD dwStyle = 0, DWORD dwExStyle = 0,                           _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) {         m_wndReflector.Create(hWndParent, rect, NULL,                         WS_VISIBLE | WS_CHILD |                                           WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0,                             Reflector::REFLECTOR_MAP_ID);                                 RECT rcPos = { 0, 0, rect.m_lpRect->right,                            rect.m_lpRect->bottom };                                      return CWindowImpl<                                                   T, TBase, TWinTraits >::Create(m_wndReflector,                    rcPos, szWindowName, dwStyle, dwExStyle,                          MenuOrID, lpCreateParam);                                 }                                                                 typedef CWindowWithReflectorImpl<                                     T, TBase, TWinTraits > thisClass;                             BEGIN_MSG_MAP(thisClass)                                              MESSAGE_HANDLER(WM_NCDESTROY, OnNcDestroy)                        MESSAGE_HANDLER(WM_WINDOWPOSCHANGING,                                 OnWindowPosChanging)                                      END_MSG_MAP()                                                     LRESULT OnNcDestroy(UINT, WPARAM, LPARAM, BOOL& bHandled) {           m_wndReflector.DestroyWindow();                                   bHandled = FALSE;                                                 return 1;                                                     }                                                                 LRESULT OnWindowPosChanging(UINT uMsg,                                WPARAM wParam, LPARAM lParam,                                     BOOL& ) {                                                         WINDOWPOS* pWP = (WINDOWPOS*)lParam;                              m_wndReflector.SetWindowPos(m_wndReflector.GetParent(),               pWP->x, pWP->y, pWP->cx, pWP->cy, pWP->flags);                pWP->flags |= SWP_NOMOVE;                                         pWP->x = 0;                                                       pWP->y = 0;                                                       return DefWindowProc(uMsg, wParam, lParam);                   }                                                                 // reflector window stuff                                         class Reflector : public CWindowImpl<Reflector> {                 public:                                                               enum { REFLECTOR_MAP_ID = 69 };                                   DECLARE_WND_CLASS_EX(_T("ATLReflectorWindow"), 0, -1)             BEGIN_MSG_MAP(Reflector)                                              REFLECT_NOTIFICATIONS()                                       END_MSG_MAP()                                                 } m_wndReflector;                                             };                                                                


This class actually creates two windows. The inner window is your actual window. The outer window is an invisible parent window that does nothing but reflect parent notifications back to the control. This extra parent window includes the REFLECT_NOTIFICATIONS macro in its message maps so you don't have to.

The best thing about this implementation is the transparency. CWindowWithReflectorImpl::Create returns the HWND of the inner control, not the invisible outer window. The parent can send messages to the control directly and doesn't have to know anything about the outer invisible window.

Message Forwarding

Just as some messages are inconveniently hard-wired to go to a parent window, sometimes messages are hard-wired to go to a window, but we'd rather let the parent handle it. This can be especially useful if you're building a composite control: a single control that contains multiple child controls. The notifications will go up to the parent control, but we might want to pass them to the composite control's parent easily.

To do this, add the FORWARD_NOTIFICATIONS() macro to your control's message map.

#define FORWARD_NOTIFICATIONS() { \                    bHandled = TRUE; \                             lResult = ForwardNotifications(uMsg, \             wParam, lParam, bHandled); \               if(bHandled) \                                     return TRUE; \                         }                                          


This macro calls the ForwardNotifications function defined in CWindowImplRoot:

template <class TBase>                                           LRESULT                                                          CWindowImplRoot< TBase >::ForwardNotifications(UINT uMsg,            WPARAM wParam, LPARAM lParam, BOOL& bHandled) {                  LRESULT lResult = 0;                                             switch(uMsg) {                                                   case WM_COMMAND:                                                 case WM_NOTIFY:                                                  case WM_PARENTNOTIFY:                                            case WM_DRAWITEM:                                                case WM_MEASUREITEM:                                             case WM_COMPAREITEM:                                             case WM_DELETEITEM:                                              case WM_VKEYTOITEM:                                              case WM_CHARTOITEM:                                              case WM_HSCROLL:                                                 case WM_VSCROLL:                                                 case WM_CTLCOLORBTN:                                             case WM_CTLCOLORDLG:                                             case WM_CTLCOLOREDIT:                                            case WM_CTLCOLORLISTBOX:                                         case WM_CTLCOLORMSGBOX:                                          case WM_CTLCOLORSCROLLBAR:                                       case WM_CTLCOLORSTATIC:                                              lResult = GetParent().SendMessage(uMsg, wParam, lParam);         break;                                                       default:                                                             bHandled = FALSE;                                                break;                                                       }                                                                return lResult;                                              }                                                                


The implementation is very simple; it forwards the message straight up to the parent window. There is no change in the message ID, so the parent window can use a normal WM_ message ID in its message map.

Between message reflection and forwarding, it's quite easy to shuffle standard notifications up and down the windowing tree as needed.

Message Chaining

If you find yourself handling messages in the same way, you might want to reuse the message-handler implementations. If you're willing to populate the message map entries yourself, there's no reason you can't use normal C++ implementation techniques:

template <typename Deriving> class CFileHandler { public:   LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);   LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);   LRESULT OnFileExit(WORD, WORD, HWND, BOOL&); }; class CMainWindow :     public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,     public CFileHandler<CMainWindow> { public: BEGIN_MSG_MAP(CMainWindow)   MESSAGE_HANDLER(WM_PAINT, OnPaint)   // Route messages to base class   COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)   COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)   COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)   COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)   COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)   COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout) END_MSG_MAP() ... }; 


This technique is somewhat cumbersome, however. If the base class gained a new message handler, such as OnFileClose, each deriving class would have to manually add an entry to its message map. We'd really like the capability to "inherit" a base class's message map as well as a base class's functionality. For this, ATL provides message chaining.

Simple Message Chaining

Message chaining is the capability to extend a class's message map by including the message map of a base class or another object altogether. The simplest macro of the message chaining family is CHAIN_MSG_MAP:

#define CHAIN_MSG_MAP(theChainClass) { \                  if (theChainClass::ProcessWindowMessage(hWnd, uMsg, \     wParam, lParam, lResult)) \                           return TRUE; \                                        }                                                       


This macro allows chaining to the message map of a base class:

template <typename Deriving> class CFileHandler { public: // Message map in base class BEGIN_MSG_MAP(CMainWindow)   COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)   COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)   COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)   COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)   COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit) END_MSG_MAP()   LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);   LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);   LRESULT OnFileExit(WORD, WORD, HWND, BOOL&); }; class CMainWindow :     public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,     public CFileHandler<CMainWindow> { public: BEGIN_MSG_MAP(CMainWindow)   MESSAGE_HANDLER(WM_PAINT, OnPaint)   COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)   // Chain to a base class   CHAIN_MSG_MAP(CFileHandler<CMainWindow>) END_MSG_MAP() ... }; 


Any base class that provides its own implementation of Process-WindowMessagefor example, with the message map macroscan be used as a chainee. Also notice that CFileHandler is parameterized by the name of the deriving class. This is useful when used with static cast to obtain a pointer to the more derived class. For example, when implementing OnFileExit, you need to destroy the window represented by the deriving class:

template <typename Deriving> LRESULT CFileHandler<Deriving>::OnFileExit(     WORD, WORD, HWND, BOOL&) {     static_cast<Deriving*>(this)->DestroyWindow();     return 0; } 


Message chaining to a base class can be extended for any number of base classes. For example, if you wanted to handle the File, Edit, and Help menus in separate base classes, you would have several chain entries:

class CMainWindow :     public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,     public CFileHandler<CMainWindow>,     public CEditHandler<CMainWinow>,     public CHelpHandler<CMainWindow> { public: BEGIN_MSG_MAP(CMainWindow)   MESSAGE_HANDLER(WM_PAINT, OnPaint)   COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)   // Chain to a base class   CHAIN_MSG_MAP(CFileHandler<CMainWindow>)   CHAIN_MSG_MAP(CEditHandler<CMainWindow>)   CHAIN_MSG_MAP(CHelpHandler<CMainWindow>) END_MSG_MAP() ... }; 


If instead of chaining to a base class message map you want to chain to the message map of a data member, you can use the CHAIN_MSG_MAP_MEMBER macro:

#define CHAIN_MSG_MAP_MEMBER(theChainMember) { \          if (theChainMember.ProcessWindowMessage(hWnd, uMsg, \     wParam, lParam, lResult)) \                           return TRUE; \                                        }                                                       


If a handler will be a data member, it needs to access a pointer to the actual object differently; a static cast won't work. For example, an updated CFileHandler takes a pointer to the window for which it's handling messages in the constructor:

template <typename TWindow> class CFileHandler { public: BEGIN_MSG_MAP(CFileHandler)   COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)   COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)   COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)   COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)   COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit) END_MSG_MAP()   CFileHandler(TWindow* pwnd) : m_pwnd(pwnd) {}   LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);   LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);   LRESULT OnFileExit(WORD, WORD, HWND, BOOL&); private:   TWindow* m_pwnd; }; 


An updated implementation would use the cached pointer to access the window instead of a static cast:

template <typename TWindow> LRESULT CFileHandler<TWindow>::OnFileExit(WORD, WORD wID,     HWND, BOOL&) {     m_pwnd->DestroyWindow();     return 0; } 


When we have an updated handler class, using it looks like this:

class CMainWindow :   public CWindowImpl<CMainWindow, CWindow, CMainWinTraits> { public: BEGIN_MSG_MAP(CMainWindow)   MESSAGE_HANDLER(WM_PAINT, OnPaint)   COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)   // Chain to the CFileHandler member   CHAIN_MSG_MAP_MEMBER(m_handlerFile) END_MSG_MAP() ... private:   CFileHandler<CMainWindow> m_handlerFile; }; 


Alternate Message Maps

It's possible to break a message map into multiple pieces. Each piece is called an alternate message map. Recall that the message map macros expand into a switch statement that switches on the dwMsgMapID parameter to ProcessWindowMessage. The main part of the message map is the first part and is identified with a zero message map ID. An alternate part of the message map, on the other hand, is distinguished with a nonzero message map ID. As each message comes in, it's routed first by message map ID and then by message. When a window receives its own messages and those messages chained from another window, an alternate part of the message map allows the window to distinguish where the messages are coming from. A message map is broken into multiple parts using the ALT_MSG_MAP macro:

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


For example, imagine a child window that is receiving messages routed to it from the main window:

class CView : public CWindowImpl<CView> { public: BEGIN_MSG_MAP(CView) // Handle CView messages   MESSAGE_HANDLER(WM_PAINT, OnPaint) // Handle messages chained from the parent window ALT_MSG_MAP(1)   COMMAND_HANDLER(ID_EDIT_COPY, OnCopy) END_MSG_MAP() ... }; 


Because the message map has been split, the child window (CView) receives only its own messages in the main part of the message map. However, if the main window were to chain messages using CHAIN_MSG_MAP_MEMBER, the child would receive the messages in the main part of the message map, not the alternate part. To chain messages to an alternate part of the message map, ATL provides two macros, CHAIN_MSG_MAP_ALT and CHAIN_MSG_MAP_ALT_MEMBER:

#define CHAIN_MSG_MAP_ALT(theChainClass, msgMapID) { \    if (theChainClass::ProcessWindowMessage(hWnd, uMsg, \     wParam, lParam, lResult, msgMapID)) \                 return TRUE; \                                        }                                                       


#define CHAIN_MSG_MAP_ALT_MEMBER(theChainMember, msgMapID) { \   if (theChainMember.ProcessWindowMessage(hWnd, uMsg, \            wParam, lParam, lResult, msgMapID)) \                        return TRUE; \                                               }                                                              


For example, for the parent window to route unhandled messages to the child, it can use CHAIN_MSG_ALT_MEMBER like this:

class CMainWindow : ... { public: BEGIN_MSG_MAP(CMainWindow)   MESSAGE_HANDLER(WM_CREATE, OnCreate)   ...   // Route unhandled messages to the child window   CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1) END_MSG_MAP()   LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) {     return m_view.Create(m_hWnd, CWindow::rcDefault) ? 0 : -1;   } ... private:   CView m_view; }; 


Dynamic Chaining

Message map chaining to a base class or a member variable is useful but not as flexible as you might like. What if you want a looser coupling between the window that sent the message and the handler of the message? For example, the MFC WM_COMMAND message routing depends on just such a loose coupling. The View receives all the WM_COMMAND messages initially, but the Document handles file-related command messages. If we want to construct such a relationship using ATL, we have one more chaining message map macro, CHAIN_MSG_MAP_DYNAMIC:

#define CHAIN_MSG_MAP_DYNAMIC(dynaChainID) { \        if (CDynamicChain::CallChain(dynaChainID, hWnd, \     uMsg, wParam, lParam, lResult)) \                 return TRUE; \                                    }                                                   


Chaining sets up a relationship between two objects that handle messages. If the object that first receives the message doesn't handle it, the second object in line can handle it. The relationship is established using a dynamic chain ID. A dynamic chain ID is a number that the primary message processor uses to identify the secondary message processor that wants to process unhandled messages. To establish the dynamic chaining relationship, two things must happen. First, the secondary message processor must derive from CMessageMap:

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


CMessageMap is actually a poorly named class. A better name would be something like CMessageHandler or even CMessageProcessor. All that CMessageMap does is guarantee that every class that derives from it will implement ProcessWindowMessage. In fact, CWindowImpl derives from it as well, making an implementation of ProcessWindowMessage mandatory for CWindowImpl-derived classes. A secondary message processor must derive from CMessageMap so that it can be placed in the ATL_CHAIN_ENTRY structure managed by the primary message processor:

struct ATL_CHAIN_ENTRY {       DWORD        m_dwChainID;    CMessageMap* m_pObject;      DWORD        m_dwMsgMapID; };                           


A primary message processor that wants to chain messages dynamically derives from CDynamicChain, a base class that manages a dynamic array of ATL_CHAIN_ENTRY structures. CDynamicChain provides two important member functions. The first, SetChainEntry, is used to add an ATL_CHAIN_ENTRY structure to the list:

BOOL                                                                CDynamicChain::SetChainEntry(DWORD dwChainID, CMessageMap* pObject,   DWORD dwMsgMapID = 0);                                            


The other important function, CallChain, is used by the CHAIN_MSG_MAP_DYNAMIC macro to chain messages to any interested parties:

BOOL                                                            CDynamicChain::CallChain(DWORD dwChainID, HWND hWnd, UINT uMsg,   WPARAM wParam, LPARAM lParam, LRESULT& lResult);              


As an example of this technique, imagine an application built using a simplified Document/View architecture. The main window acts as the frame, holding the menu bar and two other objects, a document and a view. The view manages the client area of the main window and handles the painting of the data maintained by the document. The view is also responsible for handling view-related menu commands, such as Edit | Copy. The document is responsible for maintaining the current state as well as handling document-related menu commands, such as File | Save. To route command messages to the view, the main window uses an alternate message map, member function chaining (which we've already seen). However, to continue routing commands from the view to the document, after creating both the document and the view, the main window "hooks" them together using SetChainEntry. Any messages unhandled by the view automatically are routed to the document by the CHAIN_MSG_MAP_DYNAMIC entry in the view's message map. Finally, the document handles any messages it likes, leaving unhandled messages for DefWindowProc.

The main window creates the document and view, and hooks them together:

class CMainWindow :   public CWindowImpl<CMainWindow, CWindow, CMainWinTraits> { public: BEGIN_MSG_MAP(CMainWindow)   // Handle main window messages   MESSAGE_HANDLER(WM_CREATE, OnCreate)   ...   // Route unhandled messages to the view   CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1)   // Pick up messages the view hasn't handled   COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout) END_MSG_MAP()   CMainWindow() : m_doc(this), m_view(&m_doc) {     // Hook up the document to receive messages from the view     m_view.SetChainEntry(1, &m_doc, 1);   } private:   // Create the view to handle the main window's client area   LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)   {     return (m_view.Create(m_hWnd, CWindow::rcDefault) ? 0 : -1);   }   LRESULT OnHelpAbout(WORD, WORD, HWND, BOOL&);   virtual void OnFinalMessage(HWND /*hwnd*/);   ... private:   CDocument<CMainWindow>  m_doc;   CView<CMainWindow>      m_view; }; 


The view handles the messages it wants and chains the rest to the document:

template <typename TMainWindow> class CView :   public CWindowImpl<CView>,   // Derive from CDynamicChain to support dynamic chaining   public CDynamicChain { public:   CView(CDocument<TMainWindow>* pdoc) : m_pdoc(pdoc) {     // Set the document-managed string     m_pdoc->SetString(__T("ATL Doc/View"));   } BEGIN_MSG_MAP(CView)   // Handle view messages   MESSAGE_HANDLER(WM_PAINT, OnPaint) ALT_MSG_MAP(1) // Handle messages from the main window   CHAIN_MSG_MAP_DYNAMIC(1) // Route messages to the document END_MSG_MAP() private:   LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&); private:   // View caches its own document pointer   CDocument<TMainWindow>* m_pdoc; }; 


The document handles any messages it receives from the view:

template <typename TMainWindow> class CDocument :   // Derive from CMessageMap to receive dynamically   // chained messages   public CMessageMap { public: BEGIN_MSG_MAP(CDocument) // Handle messages from the view and the main frame ALT_MSG_MAP(1)   COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)   COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)   COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)   COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)   COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit) END_MSG_MAP()   CDocument(TMainWindow* pwnd) : m_pwnd(pwnd) { *m_sz = 0; }   void SetString(LPCTSTR psz);   LPCTSTR GetString(); // Message handlers private:   LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);   LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);   LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);   LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);   private:     TMainWindow* m_pwnd;     TCHAR        m_sz[64];   }; 


Filtering Chained Messages

In many ways, ATL's CMessageMap is like MFC's CCmdTarget. However, MFC makes a distinction between command messages and noncommand messages. Although it's useful for the view and the document to participate in handling the main window's command messages, the restfor example, WM_XXXaren't nearly so useful. The view and the document manage to ignore the rest of the messages using alternate parts of their message maps, but still, it would be nice if every message weren't routed this way. Unfortunately, there's no built-in way to route only command messages using the message-chaining macros. However, a custom macro could do the trick:

#define CHAIN_COMMAND_DYNAMIC(dynaChainID) { \   if ((uMsg == WM_COMMAND) && (HIWORD(wParam) == 0) && \       CDynamicChain::CallChain(dynaChainID, hWnd, uMsg, \         wParam, lParam, lResult)) \   return TRUE; \ } 


This macro would chain only WM_COMMAND messages with a code of 0that is, menu commands, very much like MFC does. However, you'd still need corresponding equivalents for the nondynamic message-chaining macros.[5]

[5] This is left as an exercise to the very capable readers of this book.




ATL Internals. Working with ATL 8
ATL Internals: Working with ATL 8 (2nd Edition)
ISBN: 0321159624
EAN: 2147483647
Year: 2004
Pages: 172

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