Chapter 10. Windowing

ATL is not simply a set of wrapper classes for COM. In the same style, it also wraps the section of the Win32 API related to creating and manipulating windows, dialogs, and window controls. In addition to basic support to remove the grudge of Windows programming, the ATL windowing classes include such advanced features as subclassing and superclassing. Furthermore, this window support forms the basis for both COM controls and COM control containment, covered in following chapters.

The Structure of a Windows Application

A standard Windows application consists of several well-known elements:

  • The entry point, _tWinMain, which provides the HINSTANCE of the application, the command-line arguments and the flag indicating how to show the main window

  • A call to RegisterClass to register the main window class

  • A call to CreateWindow(Ex) to create the main window

  • A call to ShowWindow and UpdateWindow to show the main window

  • A message loop to dispatch messages

  • A procedure to handle the main window’s messages

  • A set of message handlers for messages that the main window is interested in handling

  • A call to DefWindowProc to let Windows handle messages that the main window is not interested in

  • A call to PostQuitMessage after the main window has been destroyed

A bare-bones example follows:

 1#include "stdafx.h" // Includes windows.h and tchar.h
 2LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 3
 4// Entry point
 5int APIENTRY _tWinMain(HINSTANCE hinst,
 6                       HINSTANCE /*hinstPrev*/,
 7                       LPTSTR    pszCmdLine,
 8                       int       nCmdShow) {
 9  // Register the main window class
10  LPCTSTR     pszMainWndClass = __T("WindowsApp");
11  WNDCLASSEX  wc = { sizeof(WNDCLASSEX) };
12  wc.style         = CS_HREDRAW | CS_VREDRAW;
13  wc.hInstance     = hinst;
14  wc.hIcon         = LoadIcon(0, IDI_APPLICATION);
15  wc.hCursor       = LoadCursor(0, IDC_ARROW);
16  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
17  wc.lpszClassName = pszMainWndClass;
18  wc.lpfnWndProc   = WndProc;
19  if( !RegisterClassEx(&wc) ) return -1;
20
21  // Create the main window
22  HWND    hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,
23                                pszMainWndClass,
24                                __T("Windows Application"),
25                                WS_OVERLAPPEDWINDOW,
26                                CW_USEDEFAULT, 0,
27                                CW_USEDEFAULT, 0,
28                                0, 0, hinst, 0);
29  if( !hwnd ) return -1;
30
31  // Show the main window
32  ShowWindow(hwnd, nCmdShow);
33  UpdateWindow(hwnd);
34
35  // Main message loop
36  MSG msg;
37  while( GetMessage(&msg, 0, 0, 0) ) {
38    TranslateMessage(&msg);
39    DispatchMessage(&msg);
40  }
41
42  return msg.wParam;
43}
44
45// Windows procedure
46LRESULT CALLBACK WndProc(HWND hwnd, UINT nMsg, WPARAM wparam,
47  LPARAM lparam) {
48  switch( nMsg ) {
49  // Message handlers for messages we're interested in
50  case WM_PAINT: {
51    PAINTSTRUCT ps;
52    HDC         hdc = BeginPaint(hwnd, &ps);
53    RECT        rect; GetClientRect(hwnd, &rect);
54    DrawText(hdc, __T("Hello, Windows"), -1, &rect,
55             DT_CENTER | DT_VCENTER | DT_SINGLELINE);
56    EndPaint(hwnd, &ps);
57  }
58  break;
59
60  // Post the quit message when main window is destroyed
61  case WM_DESTROY:
62    PostQuitMessage(0);
63  break;
64
65  // Let Windows handle messages we don't want
66  default:
67    return DefWindowProc(hwnd, nMsg, wparam, lparam);
68  break;
69  }
70
71  return 0;
72}

All Windows applications have similar requirements. These requirements can be expressed in procedural Win32 calls, as the example just showed. However, when procedural calls model an underlying object model, C++ programmers feel compelled to wrap those calls behind member functions. The Windowing part of the Win32 API (often called User32) is clearly implementing an underlying object model consisting of Window classes (represented by the WNDCLASSEX structure), Window objects (represented by the HWND), and member function invocation (represented by calls to the WndProc). For the C++ programmer adverse to the schism between a preferred object model and that of User32, ATL provides a small set of windowing classes, as shown in Figure 10.1.

Figure 10.1. UML diagram of ATL window classes

_images/10atl01.jpg

The classes in bold, CWindow, CWindowImpl, CWinTraits, CWinTraitsOR, CDialogImpl, CSimpleDialog and, CContainedWindowT, are the most important. The others, CWindowImplRoot, CWindowImplBaseT, and CDialogImplBaseT, are helper classes to separate parameterized code from invariant code. This separation helps to reduce template-related code bloat, but these classes are not a fundamental part of the ATL windowing classes. The former classes form the discussion for the bulk of the rest of this chapter.

CWindow

An HWND Wrapper

The most basic of the windowing classes in ATL is CWindow. Its chief job is to hold an HWND, which it can obtain via several member functions:

 1class CWindow {
 2public:
 3    CWindow(HWND hWnd = NULL) :
 4        m_hWnd(hWnd)
 5    { }
 6
 7    CWindow& operator=(HWND hWnd)
 8    { m_hWnd = hWnd; return *this; }
 9
10    void Attach(HWND hWndNew) {
11        ATLASSERT(m_hWnd == NULL);
12        ATLASSERT((hWndNew == NULL) || ::IsWindow(hWndNew));
13        m_hWnd = hWndNew;
14    }
15
16    HWND Create(LPCTSTR lpstrWndClass, HWND hWndParent,
17        _U_RECT rect = NULL,
18        LPCTSTR szWindowName = NULL,
19        DWORD dwStyle = 0, DWORD dwExStyle = 0,
20        _U_MENUorID MenuOrID = 0U,
21        LPVOID lpCreateParam = NULL) {
22        // Calls ::CreateWindowEx and caches result in m_hWnd
23        ...
24        return m_hWnd;
25    }
26    ...
27};

The HWND itself is available either as a public data member or via the HWND typecast operator:

1class CWindow {
2public:
3  HWND m_hWnd;
4  operator HWND() const { return m_hWnd; }
5  ...
6};

If you want to clear the HWND, you can set m_hWnd manually or use the Detach member function:

1inline HWND CWindow::Detach() {
2    HWND hWnd = m_hWnd;
3    m_hWnd = NULL;
4    return hWnd;
5}

A CWindow object represents a wrapper around the HWND, not the window itself. The CWindow destructor does not destroy the underlying window. Hence, there’s really no need to ever call Detach.

HWND Wrapper Functions

When the CWindow object has an HWND, you can make use of the rest of the CWindow class member functions. The purpose of CWindow is to act as a wrapper for all the functions of the User32 API. For every function that takes an HWND as the first argument, the CWindow class has a corresponding member function that uses the cached m_hWnd. For example, instead of the calling SetWindowText:

1void SayHello(HWND hwnd) {
2  SetWindowText(hwnd, __T("Hello"));
3}

you use the SetWindowText member function:

1void SayHello(HWND hwnd) {
2  CWindow wnd = hwnd;
3  wnd.SetWindowText(__T("Hello"));
4}

And when I said that all the User32 functions take an HWND as a first parameter, I meant all. As near as I can tell, with the exception of one function (SetForegroundWindow), the entire Windowing API is represented as a member function of CWindow. The CWindow class declaration comments break the wrapped functions into several categories:

Alert functions

Attributes

Caret functions

Clipboard functions

Coordinate-mapping functions

Dialog box item functions

Font functions

Help functions

Hot key functions

Icon functions

Menu functions

Message functions

Miscellaneous operations

Scrolling functions

Timer functions

Update and painting functions

Window-access functions

Window size and position functions

Window state functions

Window text functions

Window tree access

HWND Helper Functions

The vast majority of the CWindow member functions are merely inline wrappers on the raw functions. This means that you get the syntactic convenience of member functions without any additional runtime overhead. In addition, several helper functions above and beyond straight wrappers encapsulate common functionality that we often end up writing repeatedly:

 1class CWindow {
 2...
 3  DWORD GetStyle() const;
 4  DWORD GetExStyle() const;
 5  BOOL  ModifyStyle(DWORD dwRemove, DWORD dwAdd,
 6    UINT nFlags = 0);
 7  BOOL  ModifyStyleEx(DWORD dwRemove, DWORD dwAdd,
 8    UINT nFlags = 0);
 9  BOOL  ResizeClient(int nWidth, int nHeight,
10    BOOL bRedraw = TRUE);
11  HWND  GetDescendantWindow(int nID) const;
12  BOOL  CenterWindow(HWND hWndCenter = NULL);
13  BOOL  GetWindowText(BSTR* pbstrText);
14  BOOL  GetWindowText(BSTR& bstrText);
15  HWND  GetTopLevelParent() const;
16  HWND  GetTopLevelWindow() const;
17...
18};

Likewise, CWindow provides a number of type-safe wrappers for calling SendMessage for common messages, performing the error-prone casting chores for us:

 1class CWindow {
 2...
 3  void  SetFont(HFONT hFont, BOOL bRedraw = TRUE);
 4  HFONT GetFont() const;
 5  void  Print(HDC hDC, DWORD dwFlags) const;
 6  void  PrintClient(HDC hDC, DWORD dwFlags) const;
 7  void  SetRedraw(BOOL bRedraw = TRUE);
 8  HICON SetIcon(HICON hIcon, BOOL bBigIcon = TRUE);
 9  HICON GetIcon(BOOL bBigIcon = TRUE) const;
10  int   SetHotKey(WORD wVirtualKeyCode, WORD wModifiers);
11  DWORD GetHotKey() const;
12  void  NextDlgCtrl() const;
13  void  PrevDlgCtrl() const;
14  void  GotoDlgCtrl(HWND hWndCtrl) const;
15  void  SendMessageToDescendants(UINT message, WPARAM wParam = 0,
16    LPARAM lParam = 0, BOOL bDeep = TRUE);
17...
18};

Using CWindow

Before we can put the CWindow class to use in our sample Windows application, we have to establish support for the ATL window classes in our Win32 application. If you run the Visual Studio 2005 Win32 Project Wizard, on the second page, you’ll see a check box for Add Common Header Files for ATL, as shown in Figure 10.2.

Figure 10.2. Options for Win32 projects

[View full size image]

_images/10atl02.jpg

This check box adds the following lines to the precompiled header file stdafx.h:

1#include <atlbase.h>
2#include <atlstr.h>

The ATL windowing classes are defined in atlwin.h. The project template creates a skeleton application that doesn’t use the ATL windowing classes, so we’ll also need to include atlwin.h in the stdafx.h generated by the ATL project template:

1// stdafx.h
2...
3#include <atlwin.h>

After we’ve included atlwin.h, we can start using CWindow instead of raw Win32 calls.

The Win32 Project Wizard creates an InitInstance function that uses the Win32 API to register the window class and create the main window. We can update it to use CWindow instead like this:

 1BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {
 2  // Store instance handle in our global variable
 3  hInst = hInstance;
 4  CWindow wnd;
 5  wnd.Create(szWindowClass, 0, CWindow::rcDefault,
 6    __T("Windows Application"), WS_OVERLAPPEDWINDOW,
 7    WS_EX_CLIENTEDGE );
 8
 9  if (!wnd) {
10     return FALSE;
11  }
12
13  wnd.CenterWindow( );
14  wnd.ShowWindow( nCmdShow );
15  wnd.UpdateWindow( );
16  return TRUE;
17}

Notice that the structure of the program remains the same. The only difference is that we’re calling member functions instead of global functions. WndProc can be similarly updated:

 1LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam,
 2    LPARAM lParam) {
 3    CWindow wnd( hWnd );
 4
 5    switch (nMsg) {
 6    // ...
 7    case WM_PAINT: {
 8        PAINTSTRUCT ps;
 9        HDC hdc = wnd.BeginPaint(&ps);
10        RECT rect; wnd.GetClientRect(&rect);
11
12        DrawText(hdc, __T("Hello, Windows"), -1, &rect,
13            DT_CENTER | DT_VCENTER | DT_SINGLELINE);
14        wnd.EndPaint(&ps);
15        break;
16    }
17    // ... the rest is the same
18    return 0;
19}

CWindow is a step in the right direction. Instead of calling global functions and passing a handle, we’re now able to call member functions on an object. However, we’re still registering a Windows class instead of creating a C++ class, and we’re still handing callbacks via a WndProc instead of via member functions. To completely fulfill our desires, we need the next most important class in the ATL windowing hierarchy, CWindowImpl.

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:

 1template <class T, class TBase = CWindow,
 2          class TWinTraits = CControlWinTraits>
 3class ATL_NO_VTABLE CWindowImpl
 4    : public CWindowImplBaseT< TBase, TWinTraits > {
 5public:
 6    DECLARE_WND_CLASS(NULL)
 7
 8    HWND Create(HWND hWndParent, _U_RECT rect = NULL,
 9        LPCTSTR szWindowName = NULL,
10        DWORD dwStyle = 0, DWORD dwExStyle = 0,
11        _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
12...
13};

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:

 1HWND CWindowImpl::Create(HWND hWndParent, _U_RECT rect = NULL,
 2        LPCTSTR szWindowName = NULL,
 3        DWORD dwStyle = 0, DWORD dwExStyle = 0,
 4        _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) {
 5  // Generate a class name if one is not provided
 6  if (T::GetWndClassInfo().m_lpszOrigName == NULL)
 7    T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
 8
 9  // Register the window class if it hasn't
10  // already been registered
11  ATOM atom = T::GetWndClassInfo().Register(
12    &m_pfnSuperWindowProc);
13
14  ...
15}

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:

 1class CMainWindow : public CWindowImpl<CMainWindow> {...};
 2
 3// Entry point
 4int APIENTRY _tWinMain(HINSTANCE hinst,
 5  HINSTANCE /*hinstPrev*/,
 6  LPTSTR    pszCmdLine,
 7  int       nCmdShow) {
 8
 9  // Initialize global strings
10  LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
11
12  CMainWindow wnd;
13
14  wnd.Create( 0, CWindow::rcDefault, szTitle,
15    WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE );
16  if( !wnd ) {
17    return FALSE;
18  }
19  wnd.CenterWindow( );
20  wnd.ShowWindow( nCmdShow );
21  wnd.UpdateWindow( );
22
23  // Show the main window, run the message loop
24  ...
25
26  return msg.wParam;
27}

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:

 1struct _ATL_WNDCLASSINFOW {
 2    WNDCLASSEXW m_wc;
 3    LPCWSTR m_lpszOrigName;
 4    WNDPROC pWndProc;
 5    LPCWSTR m_lpszCursorID;
 6    BOOL m_bSystemCursor;
 7    ATOM m_atom;
 8    WCHAR m_szAutoName[5+sizeof(void)*CHAR_BIT];
 9    ATOM Register(WNDPROC* p)
10    { return AtlModuleRegisterWndClassInfoW(&_AtlWinModule,
11        &_AtlBaseModule, this, p); }
12};

The most important members of this structure are m_wc and m_atom. The m_wc member represents the window class structure – that 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:

 1#define DECLARE_WND_CLASS(WndClassName) \
 2static CWndClassInfo& GetWndClassInfo() { \
 3  static CWndClassInfo wc = { \
 4    { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, \
 5      StartWindowProc, 0, 0, NULL, NULL, NULL, \
 6      (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL \
 7    }, \
 8    NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
 9  }; \
10  return wc; \
11}

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:

 1#define DECLARE_WND_CLASS_EX(WndClassName, style, bkgnd) \
 2static CWndClassInfo& GetWndClassInfo() { \
 3  static CWndClassInfo wc = { \
 4    { sizeof(WNDCLASSEX), style, StartWindowProc, \
 5      0, 0, NULL, NULL, NULL, (HBRUSH)(bkgnd + 1), NULL, \
 6      WndClassName, NULL \
 7    }, \
 8    NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
 9  }; \
10  return wc; \
11}

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:

 1CMainWindow( ) {
 2    CWndClassInfo& wci = GetWndClassInfo();
 3
 4    if( !wci.m_atom ) {
 5        wci.m_wc.lpszMenuName = MAKEINTRESOURCE(IDC_ATLHELLOWIN);
 6        wci.m_wc.hIcon = LoadIcon(
 7            _AtlBaseModule.GetResourceInstance(),
 8            MAKEINTRESOURCE(IDI_ATLHELLOWIN));
 9        wci.m_wc.hIconSm = LoadIcon(
10            _AtlBaseModule.GetResourceInstance(),
11            MAKEINTRESOURCE(IDI_SMALL));
12        wci.m_wc.hbrBackground = CreateHatchBrush(HS_DIAGCROSS,
13            RGB(0, 0, 255));
14    }
15}

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:

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

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

1// Can't do this:
2// wci.m_wc.hCursor = LoadCursor(
3//   _AtlBaseModule.GetResourceInstance(),
4//   MAKEINTRESOURCE(IDC_BAREBONES));
5
6// Must do this:
7wci.m_bSystemCursor = FALSE;
8wci.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:

1template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0>
2class CWinTraits {
3public:
4  static DWORD GetWndStyle(DWORD dwStyle)
5  { return dwStyle == 0 ? t_dwStyle : dwStyle; }
6  static DWORD GetWndExStyle(DWORD dwExStyle)
7  { return dwExStyle == 0 ? t_dwExStyle : dwExStyle; }
8};

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:

1typedef CWinTraits<WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE>
2  CMainWinTraits;

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

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

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:

1// Use the default value of 0 for the style and the
2// extended style to get the window traits for this class.
3wnd.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:

 1typedef
 2CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
 3  WS_CLIPSIBLINGS, 0>
 4CControlWinTraits;
 5
 6typedef
 7CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
 8  WS_CLIPSIBLINGS,
 9  WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>
10CFrameWinTraits;
11
12typedef
13CWinTraits<WS_OVERLAPPEDWINDOW | WS_CHILD |
14  WS_VISIBLE | WS_CLIPCHILDREN |
15  WS_CLIPSIBLINGS,
16  WS_EX_MDICHILD>
17CMDIChildWinTraits;

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

 1template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0,
 2          class TWinTraits = CControlWinTraits>
 3class CWinTraitsOR {
 4public:
 5  static DWORD GetWndStyle(DWORD dwStyle) {
 6    return dwStyle | t_dwStyle |
 7      TWinTraits::GetWndStyle(dwStyle);
 8  }
 9  static DWORD GetWndExStyle(DWORD dwExStyle) {
10  return dwExStyle | t_dwExStyle | TWinTraits::GetWndExStyle(dwExStyle);
11  }
12};

Using CWinTraitsOR, CMainWinTraits can be redefined like so:

1// Leave CFrameWinTraits styles alone.
2// Add the WS_EX_CLIENTEDGE bit to the extended styles.
3typedef CWinTraitsOR<0, WS_EX_CLIENTEDGE, CFrameWinTraits>
4  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:

 1template <class TBase, class TWinTraits>
 2LRESULT CALLBACK
 3CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(
 4    HWND hWnd,
 5    UINT uMsg,
 6    WPARAM wParam, LPARAM lParam)
 7{
 8    CWindowImplBaseT< TBase, TWinTraits >* pThis =
 9        (CWindowImplBaseT< TBase, TWinTraits >*)
10        _AtlWinModule.ExtractCreateWndData();
11    ATLASSERT(pThis != NULL);
12    pThis->m_hWnd = hWnd;
13
14    pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
15    WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
16    WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd,
17        GWLP_WNDPROC, (LONG_PTR)pProc);
18    return pProc(hWnd, uMsg, wParam, lParam);
19}

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 HWND s 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.

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 thunk – that 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:

1class CMyWindow : public CWindowImpl<CMyWindow> {...};
2CMyWindow wnd1; wnd1.Create(...);
3CMyWindow 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

[View full size image]

_images/10atl03.jpg

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:

 1#if defined(_M_IX86)
 2PVOID __stdcall __AllocStdCallThunk(VOID);
 3VOID  __stdcall __FreeStdCallThunk(PVOID);
 4
 5#pragma pack(push,1)
 6struct _stdcallthunk {
 7    DWORD   m_mov;     // mov dword ptr [esp+0x4], pThis
 8                       // (esp+0x4 is hWnd)
 9    DWORD   m_this;    // Our CWindowImpl this pointer
10    BYTE    m_jmp;     // jmp WndProc
11    DWORD   m_relproc; // relative jmp
12
13    BOOL Init(DWORD_PTR proc, void* pThis) {
14        m_mov = 0x042444C7; //C7 44 24 0C
15        m_this = PtrToUlong(pThis);
16        m_jmp = 0xe9;
17        m_relproc = DWORD((INT_PTR)proc
18            ((INT_PTR)this+sizeof(_stdcallthunk)));
19        // write block from data cache and
20        //  flush from instruction cache
21        FlushInstructionCache(GetCurrentProcess(), this,
22            sizeof(_stdcallthunk));
23        return TRUE;
24    }
25
26    // some thunks will dynamically allocate the
27    // memory for the code
28    void* GetCodeAddress() {
29        return this;
30    }
31    void* operator new(size_t) {
32        return __AllocStdCallThunk();
33    }
34
35    void operator delete(void* pThunk) {
36        __FreeStdCallThunk(pThunk);
37    }
38};
39#pragma pack(pop)
40
41#elif defined(_M_AMD64)
42... // 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:

 1class CwndProcThunk {
 2public:
 3    _AtlCreateWndData cd;
 4    CStdCallThunk thunk;
 5
 6    BOOL Init(WNDPROC proc, void* pThis) {
 7        return thunk.Init((DWORD_PTR)proc, pThis);
 8    }
 9    WNDPROC GetWNDPROC() {
10        return (WNDPROC)thunk.GetCodeAddress();
11    }
12};

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.

_images/10atl04.jpg

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:

 1template <class TBase, class TWinTraits>
 2LRESULT CALLBACK
 3CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd,
 4    UINT uMsg, WPARAM wParam, LPARAM lParam) {
 5
 6    CWindowImplBaseT< TBase, TWinTraits >* pThis =
 7        (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
 8
 9    // set a ptr to this message and save the old value
10    _ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam);
11    const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg;
12    pThis->m_pCurrentMsg = &msg;
13
14    // pass to the message map to process
15    LRESULT lRes;
16    BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd,
17        uMsg, wParam, lParam, lRes, 0);
18
19    // restore saved value for the current message
20    ATLASSERT(pThis->m_pCurrentMsg == &msg);
21
22    // do the default processing if message was not handled
23    if(!bRet) {
24        if(uMsg != WM_NCDESTROY)
25            lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
26        else {
27            // unsubclass, if needed
28            LONG_PTR pfnWndProc = ::GetWindowLongPtr(
29                pThis->m_hWnd, GWLP_WNDPROC);
30            lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
31            if(pThis->m_pfnSuperWindowProc != ::DefWindowProc &&
32                ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC)
33                == pfnWndProc)
34                ::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC,
35                    (LONG_PTR)pThis->m_pfnSuperWindowProc);
36            // mark window as destroyed
37            pThis->m_dwState |= WINSTATE_DESTROYED;
38       }
39   }
40
41   if((pThis->m_dwState & WINSTATE_DESTROYED) && pOldMsg== NULL) {
42       // clear out window handle
43       HWND hWndThis = pThis->m_hWnd;
44       pThis->m_hWnd = NULL;
45       pThis->m_dwState &= ~WINSTATE_DESTROYED;
46       // clean up after window is destroyed
47       pThis->m_pCurrentMsg = pOldMsg;
48       pThis->OnFinalMessage(hWndThis);
49   }
50   else {
51       pThis->m_pCurrentMsg = pOldMsg;
52   }
53   return lRes;
54}

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:

1virtual BOOL
2ProcessWindowMessage(HWND hWnd, UINT uMsg,
3  WPARAM wParam, LPARAM lParam,
4  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:

 1class CMainWindow : public CWindowImpl<CMainWindow> {
 2public:
 3  virtual BOOL
 4  ProcessWindowMessage(HWND hWnd, UINT uMsg,
 5    WPARAM wParam, LPARAM lParam,
 6    LRESULT& lResult, DWORD /*dwMsgMapID*/) {
 7    BOOL bHandled = TRUE;
 8    switch( uMsg ) {
 9    case WM_PAINT:   lResult = OnPaint(); break;
10    case WM_DESTROY: lResult = OnDestroy(); break;
11    default:         bHandled = FALSE; break;
12    }
13    return bHandled;
14  }
15
16private:
17  LRESULT OnPaint(); {
18    PAINTSTRUCT ps;
19    HDC         hdc = BeginPaint(&ps);
20    RECT        rect; GetClientRect(&rect);
21    DrawText(hdc, __T("Hello, Windows"), -1, &rect,
22             DT_CENTER | DT_VCENTER | DT_SINGLELINE);
23    EndPaint(&ps);
24    return 0;
25  }
26
27  LRESULT OnDestroy() {
28    PostQuitMessage(0);
29    return 0;
30  }
31};

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:

1virtual void CMainWindow::OnFinalMessage(HWND /*hwnd*/)
2{ 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.

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:

 1#define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) \
 2static ATL::CWndClassInfo& GetWndClassInfo() \
 3{ \
 4   static ATL::CWndClassInfo wc = \
 5   { \
 6       { sizeof(WNDCLASSEX), 0, StartWindowProc, \
 7         0, 0, NULL, NULL, NULL, NULL, NULL, \
 8         WndClassName, NULL }, \
 9       OrigWndClassName, NULL, NULL, TRUE, 0, _T("") \
10   }; \
11   return wc; \
12}

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:

 1// Letters-only edit control
 2class CLetterBox : public CWindowImpl<CLetterBox> {
 3public:
 4  DECLARE_WND_SUPERCLASS(0, "EDIT")
 5
 6  virtual BOOL
 7  ProcessWindowMessage(HWND hWnd, UINT uMsg,
 8    WPARAM wParam, LPARAM lParam,
 9    LRESULT& lResult, DWORD /*dwMsgMapID*/) {
10    BOOL bHandled = TRUE;
11    switch( uMsg ) {
12    case WM_CHAR:
13      lResult = OnChar((TCHAR)wParam, bHandled); break;
14    default:
15      bHandled = FALSE; break;
16    }
17    return bHandled;
18  }
19
20private:
21  LRESULT OnChar(TCHAR c, BOOL& bHandled) {
22    if( isalpha(c) ) bHandled = FALSE;
23    else             MessageBeep(0xFFFFFFFF);
24    return 0;
25  }
26};

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:

 1#define BEGIN_MSG_MAP(theClass) \
 2public: \
 3  BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, \
 4    WPARAM wParam, LPARAM lParam, LRESULT& lResult, \
 5    DWORD dwMsgMapID = 0) \
 6  { \
 7    BOOL bHandled = TRUE; \
 8    (hWnd); (uMsg); (wParam); (lParam); \
 9    (lResult); (bHandled); \
10    switch(dwMsgMapID) \
11    { \
12    case 0:
13
14
15#define END_MSG_MAP() \
16      break; \
17    default: \
18    ATLTRACE(ATL::atlTraceWindowing, 0, \
19      _T("Invalid message map ID (%i)\n"), \
20      dwMsgMapID); \
21    ATLASSERT(FALSE); \
22    break; \
23  } \
24  return FALSE; \
25}

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:

1#define MESSAGE_HANDLER(msg, func) \
2  if(uMsg == msg) { \
3    bHandled = TRUE; \
4    lResult = func(uMsg, wParam, lParam, bHandled); \
5    if(bHandled) return TRUE; \
6  }

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

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

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

1BEGIN_MSG_MAP(CMainWindow)
2  MESSAGE_HANDLER(WM_PAINT, OnPaint)
3END_MSG_MAP()

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

 1// BEGIN_MSG_MAP(CMainWindow)
 2BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam,
 3                            LPARAM lParam, LRESULT& lResult,
 4                            DWORD dwMsgMapID = 0) {
 5    BOOL bHandled = TRUE;
 6    switch (dwMsgMapID) {
 7    case 0:
 8
 9      // MESSAGE_HANDLER(WM_PAINT, OnPaint)
10      if(uMsg == WM_PAINT) {
11        bHandled = TRUE;
12        lResult = OnPaint(uMsg, wParam, lParam, bHandled);
13        if (bHandled) return TRUE;
14      }
15
16    // END_MSG_MAP()
17      break;
18    default:
19      ATLTRACE2(atlTraceWindowing, 0,
20        _T("Invalid message map ID (%i)\n"),
21        dwMsgMapID);
22      ATLASSERT(FALSE);
23      break;
24    }
25    return FALSE;
26}

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:

1LRESULT
2MessageHandler(UINT nMsg, WPARAM wparam, LPARAM lparam,
3BOOL& 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:

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

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.

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 name – for example, EDIT – just 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:

1WM_COMMAND
2  wNotifyCode = HIWORD(wParam); // notification code
3  wID = LOWORD(wParam);         // item, control, or
4                                // accelerator identifier
5  hwndCtl = (HWND)lParam;       // handle of control
6
7WM_NOTIFY
8  idCtrl = (int)wParam;         // control identifier
9  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:

1typedef struct tagNMHDR {
2  HWND hwndFrom; // handle of the control
3  UINT idFrom;   // control identifier
4  UINT code;     // notification code
5} 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:

1LRESULT
2CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl,
3  BOOL& bHandled);
4
5LRESULT
6NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);

The most basic handler macros are COMMAND_HANDLER and NOTIFY_HANDLER:

 1#define COMMAND_HANDLER(id, code, func) \
 2  if(uMsg == WM_COMMAND && \
 3     id == LOWORD(wParam) && \
 4     code == HIWORD(wParam)) \
 5  { \
 6    bHandled = TRUE; \
 7    lResult = func(HIWORD(wParam), LOWORD(wParam), \
 8      (HWND)lParam, bHandled); \
 9    if(bHandled) return TRUE; \
10  }
11
12#define NOTIFY_HANDLER(id, code, func) \
13  if(uMsg == WM_NOTIFY && \
14     id == ((LPNMHDR)lParam)->idFrom && \
15     code == ((LPNMHDR)lParam)->code) \
16  { \
17    bHandled = TRUE; \
18    lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
19    if(bHandled) return TRUE; \
20  }

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:

1COMMAND_HANDLER(IDC_EDIT1, EN_CHANGE, OnEdit1Change)

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

1NOTIFY_HANDLER(IDC_TOOLBAR1, TBN_BEGINDRAG, OnToolbar1BeginDrag)

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

 1int APIENTRY _tWinMain(HINSTANCE hinst,
 2                       HINSTANCE /*hinstPrev*/,
 3                       LPTSTR    pszCmdLine,
 4                       int       nCmdShow) {
 5  // Initialize the ATL module
 6  ...
 7
 8  // Create the main window
 9  CMainWindow wnd;
10
11  // Load a menu
12  HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),
13                         MAKEINTRESOURCE(IDR_MENU));
14
15  // Use the value 0 for the style and the extended style
16  // to get the window traits for this class.
17  wnd.Create(0, CWindow::rcDefault, __T("Windows Application"),
18    0, 0, (UINT)hMenu);
19  if( !wnd ) return -1;
20
21  ... // The rest is the same
22}

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

 1class CMainWindow : public CWindowImpl<CMainWindow,
 2  CWindow, CMainWinTraits> {
 3public:
 4BEGIN_MSG_MAP(CMainWindow)
 5  MESSAGE_HANDLER(WM_PAINT, OnPaint)
 6  COMMAND_HANDLER(ID_FILE_EXIT, 0, OnFileExit)
 7  COMMAND_HANDLER(ID_HELP_ABOUT, 0, OnHelpAbout)
 8END_MSG_MAP()
 9...
10  LRESULT OnFileExit(WORD wNotifyCode, WORD wID, HWND hWndCtl,
11                     BOOL& bHandled);
12  LRESULT OnHelpAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl,
13                      BOOL& bHandled);
14};

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:

 1#define COMMAND_ID_HANDLER(id, func) \
 2  if(uMsg == WM_COMMAND && id == LOWORD(wParam)) \
 3  { \
 4        bHandled = TRUE; \
 5    lResult = func(HIWORD(wParam), LOWORD(wParam), \
 6      (HWND)lParam, bHandled); \
 7    if(bHandled) \
 8      return TRUE; \
 9  }
10
11#define NOTIFY_ID_HANDLER(id, func) \
12  if(uMsg == WM_NOTIFY && id == ((LPNMHDR)lParam)->idFrom) \
13  { \
14        bHandled = TRUE; \
15    lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
16    if(bHandled) \
17    return TRUE; \
18  }

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

1COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
2COMMAND_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:

 1#define COMMAND_RANGE_HANDLER(idFirst, idLast, func) \
 2  if(uMsg == WM_COMMAND && LOWORD(wParam) >= idFirst && \
 3    LOWORD(wParam) <= idLast) \
 4  { \
 5        bHandled = TRUE; \
 6    lResult = func(HIWORD(wParam), LOWORD(wParam), \
 7      (HWND)lParam, bHandled); \
 8    if(bHandled) \
 9    return TRUE; \
10  }
11
12#define NOTIFY_RANGE_HANDLER(idFirst, idLast, func) \
13  if(uMsg == WM_NOTIFY && \
14    ((LPNMHDR)lParam)->idFrom >= idFirst && \
15    ((LPNMHDR)lParam)->idFrom <= idLast) \
16  { \
17        bHandled = TRUE; \
18    lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
19    if(bHandled) \
20    return TRUE; \
21  }

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:

 1#define COMMAND_CODE_HANDLER(code, func) \
 2  if(uMsg == WM_COMMAND && code == HIWORD(wParam)) \
 3  { \
 4    bHandled = TRUE; \
 5    lResult = func(HIWORD(wParam), LOWORD(wParam), \
 6      (HWND)lParam, bHandled); \
 7    if(bHandled) \
 8    return TRUE; \
 9  }
10
11#define NOTIFY_CODE_HANDLER(cd, func) \
12  if(uMsg == WM_NOTIFY && cd == ((LPNMHDR)lParam)->code) \
13  { \
14    bHandled = TRUE; \
15    lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
16    if(bHandled) \
17    return TRUE; \
18  }

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:

1typedef struct tagNMTOOLBAR {
2    NMHDR    hdr;
3    int      iItem;
4    TBBUTTON tbButton;
5    int      cchText;
6    LPTSTR   pszText;
7} 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:

 1class CMainWindow : public CWindowImpl< CMainWindow, CWindow,
 2  CMainWinTraits >
 3{
 4public:
 5    CMainWindow( ) {
 6    }
 7
 8    BEGIN_MSG_MAP(CMainWindow)
 9        MESSAGE_HANDLER(WM_CREATE, OnCreate)
10        MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnControlColorStatic)
11    END_MSG_MAP()
12
13private:
14    LRESULT OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam,
15        BOOL& bHandled) {
16        m_staticBackground = ::CreateSolidBrush(RGB(0, 255, 0));
17
18        RECT rc;
19        rc.left = 25;
20        rc.right = 300;
21        rc.top = 25;
22        rc.bottom = 75;
23
24        if( m_message.Create(_T("static"),
25            m_hWnd, rc, _T("Static Message"),
26            WS_VISIBLE | WS_CHILD ) ) {
27            return 0;
28        }
29        return -1;
30}
31
32LRESULT OnControlColorStatic(UINT nMsg,
33    WPARAM wParam, LPARAM lParam,
34    BOOL& bHandled) {
35    HDC hdc = reinterpret_cast< HDC >( wParam );
36    ::SetTextColor(hdc, RGB(255, 0, 0) );
37    ::SetBkColor( hdc, RGB(0, 255, 0 ) );
38    return reinterpret_cast< LRESULT >(m_staticBackground);
39}
40
41void OnFinalMessage(HWND hWnd) {
42    ::DeleteObject( m_staticBackground );
43    PostQuitMessage(0);
44}
45    CWindow m_message;
46    HBRUSH m_staticBackground;
47};

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:

1#define REFLECT_NOTIFICATIONS() \
2    { \
3        bHandled = TRUE; \
4        lResult = ReflectNotifications(uMsg, \
5            wParam, lParam, bHandled); \
6        if(bHandled) \
7            return TRUE; \
8    }

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

 1template <class TBase>
 2LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg,
 3    WPARAM wParam, LPARAM lParam, BOOL& bHandled)
 4{
 5    HWND hWndChild = NULL;
 6    switch(uMsg) {
 7    case WM_COMMAND:
 8        if(lParam != NULL)    // not from a menu
 9            hWndChild = (HWND)lParam;
10        break;
11    case WM_NOTIFY:
12        hWndChild = ((LPNMHDR)lParam)->hwndFrom;
13        break;
14    case WM_PARENTNOTIFY:
15        switch(LOWORD(wParam)) {
16        case WM_CREATE:
17        case WM_DESTROY:
18            hWndChild = (HWND)lParam;
19            break;
20        default:
21            hWndChild = GetDlgItem(HIWORD(wParam));
22            break;
23        }
24        break;
25    case WM_DRAWITEM:
26        if(wParam)    // not from a menu
27            hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;
28        break;
29    case WM_MEASUREITEM:
30        if(wParam)    // not from a menu
31            hWndChild = GetDlgItem(
32                ((LPMEASUREITEMSTRUCT)lParam)->CtlID);
33        break;
34    case WM_COMPAREITEM:
35        if(wParam)    // not from a menu
36            hWndChild = ((LPCOMPAREITEMSTRUCT)lParam)->hwndItem;
37        break;
38    case WM_DELETEITEM:
39        if(wParam)    // not from a menu
40            hWndChild = ((LPDELETEITEMSTRUCT)lParam)->hwndItem;
41
42        break;
43    case WM_VKEYTOITEM:
44    case WM_CHARTOITEM:
45    case WM_HSCROLL:
46    case WM_VSCROLL:
47        hWndChild = (HWND)lParam;
48        break;
49    case WM_CTLCOLORBTN:
50    case WM_CTLCOLORDLG:
51    case WM_CTLCOLOREDIT:
52    case WM_CTLCOLORLISTBOX:
53    case WM_CTLCOLORMSGBOX:
54    case WM_CTLCOLORSCROLLBAR:
55    case WM_CTLCOLORSTATIC:
56        hWndChild = (HWND)lParam;
57        break;
58    default:
59        break;
60    }
61
62    if(hWndChild == NULL) {
63        bHandled = FALSE;
64        return 1;
65    }
66
67    ATLASSERT(::IsWindow(hWndChild));
68    return ::SendMessage(hWndChild, OCM__BASE + uMsg,
69        wParam, lParam);
70}

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:

 1class CColoredStatic : public CWindowImpl< CColoredStatic > {
 2public:
 3    DECLARE_WND_SUPERCLASS(0, _T("STATIC"))
 4
 5    CColoredStatic( COLORREF foreground, COLORREF background ) {
 6        m_foreground = foreground;
 7        m_background = background;
 8        m_backgroundBrush = ::CreateSolidBrush( m_background );
 9    }
10
11    ~CColoredStatic( ) {
12        ::DeleteObject( m_backgroundBrush );
13    }
14private:
15
16    BEGIN_MSG_MAP(CColoredStatic)
17        MESSAGE_HANDLER(OCM_CTLCOLORSTATIC, OnControlColorStatic)
18    END_MSG_MAP()
19
20    LRESULT OnControlColorStatic(UINT nMsg, WPARAM wParam,
21        LPARAM lParam, BOOL& bHandled) {
22        HDC hdc = reinterpret_cast< HDC >( wParam );
23        ::SetTextColor(hdc, m_foreground );
24        ::SetBkColor( hdc, m_background );
25        return reinterpret_cast< LRESULT >(m_backgroundBrush);
26    }
27
28    COLORREF m_foreground;
29    COLORREF m_background;
30    HBRUSH m_backgroundBrush;
31};

Our parent window class is simplified as well:

 1class CMainWindow :
 2    public CWindowImpl< CMainWindow, CWindow, CMainWinTraits >
 3{
 4public:
 5    CMainWindow( ) :
 6        m_message( RGB( 0, 0, 255 ), RGB(255, 255, 255) ) {
 7    }
 8
 9    BEGIN_MSG_MAP(CMainWindow)
10        MESSAGE_HANDLER(WM_CREATE, OnCreate)
11        REFLECT_NOTIFICATIONS()
12    END_MSG_MAP()
13...
14};

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:

1class CColoredStatic :
2    public CWindowWithReflectorImpl< CColoredStatic >
3{
4//
5... the rest is exactly the same ...
6};

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:

 1template <class T, class TBase, class TwinTraits >
 2class ATL_NO_VTABLE CWindowWithReflectorImpl :
 3    public CWindowImpl< T, TBase, TWinTraits > {
 4public:
 5    HWND Create(HWND hWndParent, _U_RECT rect = NULL,
 6        LPCTSTR szWindowName = NULL,
 7        DWORD dwStyle = 0, DWORD dwExStyle = 0,
 8        _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) {
 9        m_wndReflector.Create(hWndParent, rect, NULL,
10            WS_VISIBLE | WS_CHILD |
11            WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0,
12            Reflector::REFLECTOR_MAP_ID);
13        RECT rcPos = { 0, 0, rect.m_lpRect->right,
14            rect.m_lpRect->bottom };
15        return CWindowImpl<
16            T, TBase, TWinTraits >::Create(m_wndReflector,
17            rcPos, szWindowName, dwStyle, dwExStyle,
18            MenuOrID, lpCreateParam);
19    }
20
21    typedef CWindowWithReflectorImpl<
22        T, TBase, TWinTraits > thisClass;
23    BEGIN_MSG_MAP(thisClass)
24        MESSAGE_HANDLER(WM_NCDESTROY, OnNcDestroy)
25        MESSAGE_HANDLER(WM_WINDOWPOSCHANGING,
26            OnWindowPosChanging)
27    END_MSG_MAP()
28
29    LRESULT OnNcDestroy(UINT, WPARAM, LPARAM, BOOL& bHandled) {
30        m_wndReflector.DestroyWindow();
31        bHandled = FALSE;
32        return 1;
33    }
34
35    LRESULT OnWindowPosChanging(UINT uMsg,
36        WPARAM wParam, LPARAM lParam,
37        BOOL& ) {
38        WINDOWPOS* pWP = (WINDOWPOS*)lParam;
39        m_wndReflector.SetWindowPos(m_wndReflector.GetParent(),
40            pWP->x, pWP->y, pWP->cx, pWP->cy, pWP->flags);
41        pWP->flags |= SWP_NOMOVE;
42        pWP->x = 0;
43        pWP->y = 0;
44        return DefWindowProc(uMsg, wParam, lParam);
45    }
46
47    // reflector window stuff
48    class Reflector : public CWindowImpl<Reflector> {
49    public:
50        enum { REFLECTOR_MAP_ID = 69 };
51        DECLARE_WND_CLASS_EX(_T("ATLReflectorWindow"), 0, -1)
52        BEGIN_MSG_MAP(Reflector)
53            REFLECT_NOTIFICATIONS()
54        END_MSG_MAP()
55    } m_wndReflector;
56};

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.

1#define FORWARD_NOTIFICATIONS() { \
2        bHandled = TRUE; \
3        lResult = ForwardNotifications(uMsg, \
4            wParam, lParam, bHandled); \
5        if(bHandled) \
6            return TRUE; \
7    }

This macro calls the ForwardNotifications function defined in CWindowImplRoot:

 1template <class TBase>
 2LRESULT
 3CWindowImplRoot< TBase >::ForwardNotifications(UINT uMsg,
 4    WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
 5    LRESULT lResult = 0;
 6    switch(uMsg) {
 7    case WM_COMMAND:
 8    case WM_NOTIFY:
 9    case WM_PARENTNOTIFY:
10    case WM_DRAWITEM:
11    case WM_MEASUREITEM:
12    case WM_COMPAREITEM:
13    case WM_DELETEITEM:
14    case WM_VKEYTOITEM:
15    case WM_CHARTOITEM:
16    case WM_HSCROLL:
17    case WM_VSCROLL:
18    case WM_CTLCOLORBTN:
19    case WM_CTLCOLORDLG:
20    case WM_CTLCOLOREDIT:
21    case WM_CTLCOLORLISTBOX:
22    case WM_CTLCOLORMSGBOX:
23    case WM_CTLCOLORSCROLLBAR:
24    case WM_CTLCOLORSTATIC:
25        lResult = GetParent().SendMessage(uMsg, wParam, lParam);
26        break;
27    default:
28        bHandled = FALSE;
29        break;
30    }
31    return lResult;
32}

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:

 1template <typename Deriving>
 2class CFileHandler {
 3public:
 4  LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
 5  LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
 6  LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
 7  LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
 8  LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
 9};
10
11class CMainWindow :
12    public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,
13    public CFileHandler<CMainWindow>
14{
15public:
16BEGIN_MSG_MAP(CMainWindow)
17  MESSAGE_HANDLER(WM_PAINT, OnPaint)
18  // Route messages to base class
19  COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
20  COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
21  COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
22  COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
23  COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
24  COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
25END_MSG_MAP()
26...
27};

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:

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

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

 1template <typename Deriving>
 2class CFileHandler {
 3public:
 4// Message map in base class
 5BEGIN_MSG_MAP(CMainWindow)
 6  COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
 7  COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
 8  COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
 9  COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
10  COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
11END_MSG_MAP()
12
13  LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
14  LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
15  LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
16  LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
17  LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
18};
19
20class CMainWindow :
21    public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,
22    public CFileHandler<CMainWindow>
23{
24public:
25BEGIN_MSG_MAP(CMainWindow)
26  MESSAGE_HANDLER(WM_PAINT, OnPaint)
27  COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
28  // Chain to a base class
29  CHAIN_MSG_MAP(CFileHandler<CMainWindow>)
30END_MSG_MAP()
31...
32};

Any base class that provides its own implementation of Process-WindowMessage – for example, with the message map macro – scan 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:

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

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:

 1class CMainWindow :
 2    public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,
 3    public CFileHandler<CMainWindow>,
 4    public CEditHandler<CMainWinow>,
 5    public CHelpHandler<CMainWindow>
 6{
 7public:
 8BEGIN_MSG_MAP(CMainWindow)
 9  MESSAGE_HANDLER(WM_PAINT, OnPaint)
10  COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
11  // Chain to a base class
12  CHAIN_MSG_MAP(CFileHandler<CMainWindow>)
13  CHAIN_MSG_MAP(CEditHandler<CMainWindow>)
14  CHAIN_MSG_MAP(CHelpHandler<CMainWindow>)
15END_MSG_MAP()
16...
17};

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:

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

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:

 1template <typename TWindow>
 2class CFileHandler {
 3public:
 4BEGIN_MSG_MAP(CFileHandler)
 5  COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
 6  COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
 7  COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
 8  COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
 9  COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
10END_MSG_MAP()
11
12  CFileHandler(TWindow* pwnd) : m_pwnd(pwnd) {}
13
14  LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
15  LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
16  LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
17  LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
18  LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
19
20private:
21  TWindow* m_pwnd;
22};

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

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

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

 1class CMainWindow :
 2  public CWindowImpl<CMainWindow, CWindow, CMainWinTraits> {
 3public:
 4BEGIN_MSG_MAP(CMainWindow)
 5  MESSAGE_HANDLER(WM_PAINT, OnPaint)
 6  COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
 7  // Chain to the CFileHandler member
 8  CHAIN_MSG_MAP_MEMBER(m_handlerFile)
 9END_MSG_MAP()
10...
11private:
12  CFileHandler<CMainWindow> m_handlerFile;
13};

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:

1#define ALT_MSG_MAP(msgMapID) \
2  break; \
3  case msgMapID:

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

 1class CView : public CWindowImpl<CView> {
 2public:
 3BEGIN_MSG_MAP(CView)
 4// Handle CView messages
 5  MESSAGE_HANDLER(WM_PAINT, OnPaint)
 6
 7// Handle messages chained from the parent window
 8ALT_MSG_MAP(1)
 9  COMMAND_HANDLER(ID_EDIT_COPY, OnCopy)
10END_MSG_MAP()
11...
12};

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:

1#define CHAIN_MSG_MAP_ALT(theChainClass, msgMapID) { \
2  if (theChainClass::ProcessWindowMessage(hWnd, uMsg, \
3    wParam, lParam, lResult, msgMapID)) \
4  return TRUE; \
5}
1#define CHAIN_MSG_MAP_ALT_MEMBER(theChainMember, msgMapID) { \
2  if (theChainMember.ProcessWindowMessage(hWnd, uMsg, \
3    wParam, lParam, lResult, msgMapID)) \
4  return TRUE; \
5}

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

 1class CMainWindow : ... {
 2public:
 3BEGIN_MSG_MAP(CMainWindow)
 4  MESSAGE_HANDLER(WM_CREATE, OnCreate)
 5  ...
 6  // Route unhandled messages to the child window
 7  CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1)
 8END_MSG_MAP()
 9
10  LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
11    return m_view.Create(m_hWnd, CWindow::rcDefault) ? 0 : -1;
12  }
13...
14private:
15  CView m_view;
16};

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:

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

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:

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

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:

1struct ATL_CHAIN_ENTRY {
2  DWORD        m_dwChainID;
3  CMessageMap* m_pObject;
4  DWORD        m_dwMsgMapID;
5};

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:

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

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

1BOOL
2CDynamicChain::CallChain(DWORD dwChainID, HWND hWnd, UINT uMsg,
3  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:

 1class CMainWindow :
 2  public CWindowImpl<CMainWindow, CWindow, CMainWinTraits> {
 3public:
 4BEGIN_MSG_MAP(CMainWindow)
 5  // Handle main window messages
 6  MESSAGE_HANDLER(WM_CREATE, OnCreate)
 7  ...
 8  // Route unhandled messages to the view
 9  CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1)
10
11  // Pick up messages the view hasn't handled
12  COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
13END_MSG_MAP()
14
15  CMainWindow() : m_doc(this), m_view(&m_doc) {
16    // Hook up the document to receive messages from the view
17    m_view.SetChainEntry(1, &m_doc, 1);
18  }
19
20private:
21  // Create the view to handle the main window's client area
22  LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
23  {
24    return (m_view.Create(m_hWnd, CWindow::rcDefault) ? 0 : -1);
25  }
26
27  LRESULT OnHelpAbout(WORD, WORD, HWND, BOOL&);
28  virtual void OnFinalMessage(HWND /*hwnd*/);
29  ...
30private:
31  CDocument<CMainWindow>  m_doc;
32  CView<CMainWindow>      m_view;
33};

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

 1template <typename TMainWindow>
 2class CView :
 3  public CWindowImpl<CView>,
 4  // Derive from CDynamicChain to support dynamic chaining
 5  public CDynamicChain {
 6public:
 7  CView(CDocument<TMainWindow>* pdoc) : m_pdoc(pdoc) {
 8    // Set the document-managed string
 9    m_pdoc->SetString(__T("ATL Doc/View"));
10  }
11
12BEGIN_MSG_MAP(CView)
13  // Handle view messages
14  MESSAGE_HANDLER(WM_PAINT, OnPaint)
15ALT_MSG_MAP(1) // Handle messages from the main window
16  CHAIN_MSG_MAP_DYNAMIC(1) // Route messages to the document
17END_MSG_MAP()
18
19private:
20  LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&);
21
22private:
23  // View caches its own document pointer
24  CDocument<TMainWindow>* m_pdoc;
25};

The document handles any messages it receives from the view:

 1template <typename TMainWindow>
 2class CDocument :
 3  // Derive from CMessageMap to receive dynamically
 4  // chained messages
 5  public CMessageMap {
 6public:
 7BEGIN_MSG_MAP(CDocument)
 8
 9// Handle messages from the view and the main frame
10ALT_MSG_MAP(1)
11  COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
12  COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
13  COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
14  COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
15  COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
16END_MSG_MAP()
17
18  CDocument(TMainWindow* pwnd) : m_pwnd(pwnd) { *m_sz = 0; }
19
20  void SetString(LPCTSTR psz);
21  LPCTSTR GetString();
22
23// Message handlers
24private:
25  LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
26  LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
27  LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
28  LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
29  LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
30  private:
31    TMainWindow* m_pwnd;
32    TCHAR        m_sz[64];
33  };

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 rest – for example, WM_XXX – aren’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:

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

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

CDialogImpl

Dialogs represent a declarative style of user interface development. Whereas normal windows provide all kinds of flexibility (you can put anything you want in the client area of a window), dialogs are more static. Actually, dialogs are just windows whose layout has been predetermined. The built-in dialog box window class knows how to interpret dialog box resources to create and manage the child windows that make up a dialog box. To show a dialog box modally – that is, while the dialog is visible, the parent is inaccessible – Windows provides the DialogBoxParam [6] function:

1int DialogBoxParam(
2  HINSTANCE hInstance,    // handle to application instance
3  LPCTSTR   lpTemplate,   // identifies dialog box template
4  HWND      hWndParent,   // handle to owner window
5  DLGPROC   lpDialogFunc, // pointer to dialog box procedure
6  LPARAM    dwInitParam); // initialization value

The result of the DialogBoxParam function is the command that closed the dialog, such as IDOK or IDCANCEL. To show the dialog box modelessly – that is, the parent window is still accessible while the dialog is showing – the CreateDialogParam [7] function is used instead:

1HWND CreateDialogParam(
2  HINSTANCE hInstance,    // handle to application instance
3  LPCTSTR   lpTemplate,   // identifies dialog box template
4  HWND      hWndParent,   // handle to owner window
5  DLGPROC   lpDialogFunc, // pointer to dialog box procedure
6  LPARAM    dwInitParam); // initialization value

Notice that the parameters to CreateDialogParam are identical to those to DialogBoxParam, but the return value is different. The return value from Create-DialogParam represents the HWND of the new dialog box window, which will live until the dialog box window is destroyed.

Regardless of how a dialog is shown, however, developing a dialog is the same. First, you lay out a dialog resource using your favorite resource editor. Second, you develop a dialog box procedure (DlgProc). The WndProc of the dialog box class calls the DlgProc to give you an opportunity to handle each message (although you never have to call DefWindowProc in a DlgProc). Third, you call either DialogBoxParam or CreateDialogParam (or one of the variants) to show the dialog. This is the same kind of grunt work we had to do when we wanted to show a window (that is, register a window class, develop a WndProc, and create the window). And just as ATL lets us work with CWindow-derived objects instead of raw windows, ATL also lets us work with CDialogImpl-derived classes instead of raw dialogs.

Showing a Dialog

CDialogImpl provides a set of wrapper functions around common dialog operations (such as DialogBoxParam and CreateDialogParam):

 1template <class T, class TBase /* = CWindow */>
 2class ATL_NO_VTABLE CDialogImpl :
 3    public CDialogImplBaseT< TBase > {
 4public:
 5    // modal dialogs
 6    INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow(),
 7        LPARAM dwInitParam = NULL) {
 8        BOOL result;
 9
10        result = m_thunk.Init(NULL,NULL);
11        if (result == FALSE) {
12            SetLastError(ERROR_OUTOFMEMORY);
13            return -1;
14        }
15
16        _AtlWinModule.AddCreateWndData(&m_thunk.cd,
17            (CDialogImplBaseT< TBase >*)this);
18
19        return ::DialogBoxParam(
20            _AtlBaseModule.GetResourceInstance(),
21            MAKEINTRESOURCE(static_cast<T*>(this)->IDD),
22            hWndParent, T::StartDialogProc, dwInitParam);
23    }
24
25    BOOL EndDialog(int nRetCode) {
26        return ::EndDialog(m_hWnd, nRetCode);
27    }
28
29    // modeless dialogs
30    HWND Create(HWND hWndParent, LPARAM dwInitParam = NULL) {
31        BOOL result;
32
33        result = m_thunk.Init(NULL,NULL);
34        if (result == FALSE) {
35            SetLastError(ERROR_OUTOFMEMORY);
36            return NULL;
37        }
38
39        _AtlWinModule.AddCreateWndData(&m_thunk.cd,
40        (CDialogImplBaseT< TBase >*)this);
41
42    HWND hWnd = ::CreateDialogParam(_AtlBaseModule.
43                  GetResourceInstance(),
44        MAKEINTRESOURCE(static_cast<T*>(this)->IDD),
45        hWndParent, T::StartDialogProc, dwInitParam);
46    return hWnd;
47    }
48
49    // for CComControl
50    HWND Create(HWND hWndParent, RECT&,
51        LPARAM dwInitParam = NULL)
52    {
53        return Create(hWndParent, dwInitParam);
54    }
55
56    BOOL DestroyWindow()
57    {
58        return ::DestroyWindow(m_hWnd);
59    }
60};

A couple of interesting things are going on in this small class. First, notice the use of the thunk. ATL sets up a thunk between Windows and the ProcessWindowMessage member function of your CDialogImpl-based objects, just as it does for CWindowImpl-based objects. In addition to all the tricks that WindowProc performs (see the section “The Window Procedure,” earlier in this chapter), the static member function CDialogImpl::DialogProc manages the weirdness of DWL_MSGRESULT. For some dialog messages, the DlgProc must return the result of the message. For others, the result is set by calling SetWindowLong with DWL_MSGRESULT. And although I can never remember which is which, ATL can. Our dialog message handlers need only return the value; if it needs to go into the DWL_MSGRESULT, ATL puts it there.

Something else interesting to notice is that CDialogImpl derives from CDialogImplBaseT, which provides some other helper functions:

 1template <class TBase>
 2class ATL_NO_VTABLE CDialogImplBaseT :
 3  public CWindowImplRoot<TBase> {
 4public:
 5  virtual WNDPROC GetDialogProc() { return DialogProc; }
 6  static LRESULT CALLBACK StartDialogProc(HWND, UINT,
 7    WPARAM, LPARAM);
 8  static LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);
 9  LRESULT DefWindowProc() { return 0; }
10  BOOL MapDialogRect(LPRECT pRect) {
11    return ::MapDialogRect(m_hWnd, pRect);
12  }
13  virtual void OnFinalMessage(HWND /*hWnd*/) {}
14};

Again, notice the use of the StartDlgProc to bootstrap the thunk and the DialogProc function that actually does the mapping to the ProcessWindowMessage member function. Also notice the DefWindowProc member function. Remember, for DlgProcs, there’s no need to pass an unhandled message to DefWindowProc. Because the message-handling infrastructure of ATL requires a DefWindowProc, the CDialogImplBaseT class provides an inline, do-nothing function that the compiler is free to toss away.

More useful to the dialog developer are the CDialogImplBaseT member functions MapDialogRect and OnFinalMessage. MapDialogRect is a wrapper around the Windows function MapDialogRect, which maps dialog-box units to pixels. Finally, OnFinalMessage is called after the last dialog message has been processed, just like the CWindowImpl::OnFinalMessage member function.

You might wonder how far the inheritance hierarchy goes for CDialogImpl. Refer again to Figure 10.1. Notice that CDialogImpl ultimately derives from CWindow, so all the wrappers and helpers that are available for windows are also available to dialogs.

Before we get too far away from CDialogImpl, notice where the dialog resource identifier comes from. The deriving class is required to provide a numeric symbol called IDD indicating the resource identifier. For example, assuming a resource ID of IDD_ABOUTBOX, a typical “about” box would be implemented like this:

 1class CAboutBox : public CDialogImpl<CAboutBox> {
 2public:
 3BEGIN_MSG_MAP(CAboutBox)
 4  MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
 5  COMMAND_ID_HANDLER(IDOK, OnOK);
 6END_MSG_MAP()
 7
 8  enum { IDD = IDD_ABOUTBOX };
 9
10private:
11  LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
12    CenterWindow();
13    return 1;
14  }
15  LRESULT OnOK(WORD, UINT, HWND, BOOL&) {
16    EndDialog(IDOK);
17    return 0;
18  }
19};

CAboutBox has all the elements. It derives from CDialogImpl, has a message map, and provides a value for IDD that indicates the resource ID. You can add an ATL dialog object from the Add Item option in Visual Studio, but it’s not difficult to do by hand.

Using a CDialogImpl-derived class is a matter of creating an instance of the class and calling either DoModal or Create:

1LRESULT CMainWindow::OnHelpAbout(WORD, WORD, HWND, BOOL&) {
2  CAboutBox dlg;
3  dlg.DoModal();
4  return 0;
5}

Simple Dialogs

For simple modal dialogs, such as “about” boxes, that don’t have interaction requirements beyond the standard buttons (such as OK and Cancel), ATL provides CSimpleDialog:

 1template <WORD t_wDlgTemplateID, BOOL t_bCenter /* = TRUE */>
 2class CSimpleDialog : public CDialogImplBase {
 3public:
 4    INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow()) {
 5        _AtlWinModule.AddCreateWndData(&m_thunk.cd,
 6           (CDialogImplBase*)this);
 7
 8        INT_PTR nRet =
 9            ::DialogBox(_AtlBaseModule.GetResourceInstance(),
10                MAKEINTRESOURCE(t_wDlgTemplateID), hWndParent,
11                StartDialogProc);
12        m_hWnd = NULL;
13        return nRet;
14    }
15
16    typedef CSimpleDialog<t_wDlgTemplateID, t_bCenter> thisClass;
17    BEGIN_MSG_MAP(thisClass)
18        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
19        COMMAND_RANGE_HANDLER(IDOK, IDNO, OnCloseCmd)
20    END_MSG_MAP()
21
22    LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
23        // initialize controls in dialog with
24        // DLGINIT resource section
25        ExecuteDlgInit(t_wDlgTemplateID);
26        if(t_bCenter)
27            CenterWindow(GetParent());
28        return TRUE;
29    }
30
31    LRESULT OnCloseCmd(WORD, WORD wID, HWND, BOOL& ) {
32        ::EndDialog(m_hWnd, wID);
33        return 0;
34    }
35};

Notice that the resource ID is passed as a template parameter, as is a flag indicating whether the dialog should be centered. This reduces the definition of the CAboutBox class to the following type definition:

1typedef CSimpleDialog<IDD_ABOUTBOX> CAboutBox;

However, the use of CAboutBox remains the same.

The call to ExecuteDlgInit in the OnInitDialog method is worth mentioning. When you build a dialog using the Visual Studio dialog editor and you add a combo box, the contents of that combo box are not stored in the regular Dialog resource. Instead, they’re stored in a custom resource type named RT_DLGINIT. The normal dialog APIs completely ignore this initialization data. ExecuteDlgInit contains the code needed to read the RT_DLGINIT resource and properly initialize any combo boxes on the dialog.

Data Exchange and Validation

Unfortunately, most dialogs aren’t simple. In fact, most are downright complicated. This complication is mostly due to two reasons: writing data to child controls managed by the dialog, and reading data from child controls managed by the dialog. Exchanging data with a modal dialog typically goes like this:

  1. The application creates an instance of a CDialogImpl-derived class.

  2. The application copies some data into the dialog object’s data members.

  3. The application calls DoModal.

  4. The dialog handles WM_INITDIALOG by copying data members into child controls.

  5. The dialog handles the OK button by validating that data held by child controls. If the data is not valid, the dialog complains to the users and makes them keep trying until either they get it right or they get frustrated and click the Cancel button.

  6. If the data is valid, the data is copied back into the dialog’s data members, and the dialog ends.

  7. If the application gets IDOK from DoModal, it copies the data from the dialog data members over its own copy.

If the dialog is to be shown modelessly, the interaction between the application and the dialog is a little different, but the relationship between the dialog and its child controls is the same. A modeless dialog sequence goes like this (differences from the modal case are shown in italics):

  1. The application creates an instance of a CDialogImpl-derived class.

  2. The application copies some data into the dialog object’s data members.

  3. The applications calls ``Create``.

  4. The dialog handles WM_INITDIALOG by copying data members into child controls.

  5. The dialog handles the ``Apply`` button by validating that data held by child controls. If the data is not valid, the dialog complains to the users and makes them keep trying until either they get it right or they get frustrated and click the Cancel button.

  6. If the data is valid, the data is copied back into the dialog’s data members and the application is notified [8] to read the updated data from the dialog.

  7. When the application is notified, it copies the data from the dialog data members over its own copy.

Whether modal or modeless, the dialog’s job is to exchange data between its data members and the child controls, and to validate it along the way. MFC has something called DDX/DDV (Dialog Data eXchange/Dialog Data Validation) for just this purpose. ATL has no such support, but it turns out to be quite easy to build yourself. For example, to beef up our standalone windows application sample, imagine a dialog that allows us to modify the display string, as shown in Figure 10.5.

Figure 10.5. A dialog that needs to manage data exchange and validation

_images/10atl05.jpg

The CDialogImpl-based class looks like this:

 1class CStringDlg : public CDialogImpl<CStringDlg> {
 2public:
 3  CStringDlg() { *m_sz = 0; }
 4
 5BEGIN_MSG_MAP(CStringDlg)
 6  MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
 7  COMMAND_ID_HANDLER(IDOK, OnOK)
 8  COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
 9END_MSG_MAP()
10
11  enum { IDD = IDD_SET_STRING };
12  TCHAR m_sz[64];
13
14private:
15  bool CheckValidString() {
16    // Check the length of the string
17    int cchString =
18      ::GetWindowTextLength(GetDlgItem(IDC_STRING));
19    return cchString ? true : false;
20  }
21
22  LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
23    CenterWindow();
24
25    // Copy the string from the data member
26    // to the child control (DDX)
27    SetDlgItemText(IDC_STRING, m_sz);
28
29    return 1; // Let dialog manager set initial focus
30  }
31
32  LRESULT OnOK(WORD, UINT, HWND, BOOL&) {
33    // Complain if the string is of zero length (DDV)
34    if( !CheckValidString() ) {
35      MessageBox("Please enter a string", "Hey!");
36      return 0;
37    }
38   // Copy the string from the child control
39   // to the data member (DDX)
40   GetDlgItemText(IDC_STRING, m_sz, lengthof(m_sz));
41
42   EndDialog(IDOK);
43   return 0;
44  }
45
46  LRESULT OnCancel(WORD, UINT, HWND, BOOL&) {
47    EndDialog(IDCANCEL);
48    return 0;
49  }
50};

In this example, DDX-like functionality happens in OnInitDialog and OnOK. OnInitDialog copies the data from the data member into the child edit control. Likewise, OnOK copies the data from the child edit control back to the data member and ends the dialog if the data is valid. The validity of the data (DDV-like) is checked before the call to EndDialog in OnOK by calling the helper function CheckValidString. I decided that a zero-length string would be too boring, so I made it invalid. In this case, OnOK puts up a message box and doesn’t end the dialog. To be fair, MFC would have automated all this with a macro-based table, which makes handling a lot of DDX/DDV chores easier, but ATL certainly doesn’t prohibit data exchange or validation. The WTL library mentioned earlier also has rich DDX/DDV support.

I can do even better in the data validation area with this example. This sampleand MFC-based DDX/DDVvalidates only when the user presses the OK button, but sometimes it’s handy to validate as the user enters the data. For example, by handling EN_CHANGE notifications from the edit control, I can check for a zero-length string as the user enters it. If the string ever gets to zero, disabling the OK button would make it impossible for the user to attempt to commit the data at all, making the complaint dialog unnecessary. The following updated sample shows this technique:

 1class CStringDlg : public CDialogImpl<CStringDlg> {
 2public:
 3  ...
 4BEGIN_MSG_MAP(CStringDlg)
 5  ...
 6  COMMAND_HANDLER(IDC_STRING, EN_CHANGE, OnStringChange)
 7END_MSG_MAP()
 8private:
 9  void CheckValidString() {
10    // Check the length of the string
11    int cchString =
12      ::GetWindowTextLength(GetDlgItem(IDC_STRING));
13
14    // Enable the OK button only if the string
15    // is of non-zero length
16    ::EnableWindow(GetDlgItem(IDOK), cchString ? TRUE : FALSE);
17  }
18
19  LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
20    CenterWindow();
21
22    // Copy the string from the data member
23    // to the child control (DDX)
24    SetDlgItemText(IDC_STRING, m_sz);
25
26    // Check the string length (DDV)
27    CheckValidString();
28
29    return 1; // Let dialog manager set initial focus
30  }
31
32LRESULT OnOK(WORD, UINT, HWND, BOOL&) {
33    // Copy the string from the child control to the data member (DDX)
34    GetDlgItemText(IDC_STRING, m_sz, lengthof(m_sz));
35
36    EndDialog(IDOK);
37    return 0;
38  }
39
40  LRESULT OnStringChange(WORD, UINT, HWND, BOOL&) {
41    // Check the string length each time it changes (DDV)
42    CheckValidString();
43    return 0;
44  }
45
46  ... // The rest is the same
47};

In this case, notice that OnInitDialog takes on some DDV responsibilities and OnOK loses some. In OnInitDialog, if the string starts with a zero length, the OK button is immediately disabled. In the OnStringChange handler for EN_CHANGE, as the text in the edit control changes, we revalidate the data, enabling or disabling the OK button as necessary. Finally, we know that if we reach the OnOK handler, the OK button must be enabled and the DDV chores must already have been done. Neither ATL nor MFC can help us with this kind of DDV, but neither hinders us from providing a UI that handles both DDX and DDV in a friendly way.

Window Control Wrappers

Child Window Management

You might have noticed in the last two samples that whenever I needed to manipulate a child control – for example, when getting and setting the edit control’s text, or enabling and disabling the OK button – I used a dialog item function. The ultimate base class of CDialogImpl, CWindow, provides a number of helper functions to manipulate child controls:

 1class CWindow {
 2public:
 3  ...
 4  // Dialog-Box Item Functions
 5
 6  BOOL CheckDlgButton(int nIDButton, UINT nCheck);
 7  BOOL CheckRadioButton(int nIDFirstButton, int nIDLastButton,
 8                        int nIDCheckButton);
 9
10  int DlgDirList(LPTSTR lpPathSpec, int nIDListBox,
11    int nIDStaticPath, UINT nFileType);
12  int DlgDirListComboBox(LPTSTR lpPathSpec, int nIDComboBox,
13    int nIDStaticPath, UINT nFileType);
14
15  BOOL DlgDirSelect(LPTSTR lpString, int nCount,
16    int nIDListBox);
17  BOOL DlgDirSelectComboBox(LPTSTR lpString, int nCount,
18    int nIDComboBox);
19
20  UINT GetDlgItemInt(int nID, BOOL* lpTrans = NULL,
21                     BOOL bSigned = TRUE) const;
22  UINT GetDlgItemText(int nID, LPTSTR lpStr,
23    int nMaxCount) const;
24  BOOL GetDlgItemText(int nID, BSTR& bstrText) const;
25
26  HWND GetNextDlgGroupItem(HWND hWndCtl,
27    BOOL bPrevious = FALSE) const;
28  HWND GetNextDlgTabItem(HWND hWndCtl,
29    BOOL bPrevious = FALSE) const;
30
31  UINT IsDlgButtonChecked(int nIDButton) const;
32  LRESULT SendDlgItemMessage(int nID, UINT message,
33    WPARAM wParam = 0, LPARAM lParam = 0);
34
35  BOOL SetDlgItemInt(int nID, UINT nValue, BOOL bSigned = TRUE);
36  BOOL SetDlgItemText(int nID, LPCTSTR lpszString);
37};

ATL adds no overhead to these functions, but because they’re just inline wrappers of Windows functions, you can feel that something’s not quite as efficient as it could be when you use one of these functions. Every time we pass in a child control ID, the window probably does a lookup to figure out the HWND and then calls the actual function on the window. For example, if I ran the zoo, SetDlgItemText would be implemented like this:

1BOOL SetDlgItemText(HWND hwndParent, int nID,
2  LPCTSTR lpszString) {
3  HWND hwndChild = GetDlgItem(hwndParent, nID);
4  if( !hwndChild ) return FALSE;
5  return SetWindowText(hwndChild, lpszString);
6}

That implementation is fine for family, but when your friends come over, it’s time to pull out the good dishes. I’d prefer to cache the HWND and use SetWindowText. Plus, if I want to refer to a dialog and or a main window with an HWND, why would I want to refer to my child windows with UINT? Instead, I find it convenient to wrap windows created by the dialog manager with CWindow objects in OnInitDialog:

 1LRESULT CStringDlg::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
 2  CenterWindow();
 3
 4  // Cache HWNDs
 5  m_edit.Attach(GetDlgItem(IDC_STRING));
 6  m_ok.Attach(GetDlgItem(IDOK));
 7
 8  // Copy the string from the data member
 9  // to the child control (DDX)
10  m_edit.SetWindowText(m_sz);
11
12  // Check the string length (DDV)
13  CheckValidString();
14
15  return 1; // Let dialog manager set initial focus
16}

Now, the functions that used any of the dialog item family of functions can use CWindow member functions instead:

 1void CStringDlg::CheckValidString() {
 2  // Check the length of the string
 3  int cchString = m_edit.GetWindowTextLength();
 4
 5  // Enable the OK button only if the string is
 6  // of non-zero length
 7  m_ok.EnableWindow(cchString ? TRUE : FALSE);
 8}
 9
10LRESULT CStringDlg::OnOK(WORD, UINT, HWND, BOOL&) {
11  // Copy the string from the child control to
12  // the data member (DDX)
13  m_edit.GetWindowText(m_sz, lengthof(m_sz));
14
15  EndDialog(IDOK);
16  return 0;
17}

A Better Class of Wrappers

Now, my examples have been purposefully simple. A dialog box with a single edit control is not much work, no matter how you build it. However, let’s mix things up a little. What if we were to build a dialog with a single list box control instead, as shown in Figure 10.6?

Figure 10.6. A dialog with a list box

_images/10atl06.jpg

This simple change makes the implementation more complicated. Instead of being able to use SetWindowText, as we could with an edit control, we must use special window messages to manipulate this list box control. For example, populating the list box and setting the initial selection involves the following code:

 1LRESULT
 2CStringListDlg::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
 3  CenterWindow();
 4  // Cache list box HWND
 5  m_lb.Attach(GetDlgItem(IDC_LIST));
 6
 7  // Populate the list box
 8  m_lb.SendMessage(LB_ADDSTRING, 0,
 9    (LPARAM)__T("Hello, ATL"));
10  m_lb.SendMessage(LB_ADDSTRING, 0,
11    (LPARAM)__T("Ain't ATL Cool?"));
12  m_lb.SendMessage(LB_ADDSTRING, 0,
13    (LPARAM)__T("ATL is your friend"));
14
15  // Set initial selection
16  int n = m_lb.SendMessage(LB_FINDSTRING, 0, (LPARAM)m_sz);
17  if( n == LB_ERR ) n = 0;
18  m_lb.SendMessage(LB_SETCURSEL, n);
19
20  return 1; // Let dialog manager set initial focus
21}

Likewise, pulling out the final selection in OnOK is just as much fun:

1LRESULT CStringListDlg::OnOK(WORD, UINT, HWND, BOOL&) {
2  // Copy the selected item
3  int n = m_lb.SendMessage(LB_GETCURSEL);
4  if( n == LB_ERR ) n = 0;
5  m_lb.SendMessage(LB_GETTEXT, n, (LPARAM)(LPCTSTR)m_sz);
6
7  EndDialog(IDOK);
8  return 0;
9}

The problem is that although CWindow provides countless wrapper functions that are common to all windows, it does not provide any wrappers for the built-in Windows controls (for example, list boxes). And although MFC provides such wrapper classes as CListBox, ATL doesn’t. However, unofficially, buried deep in the atlcon [9] sample lives an undocumented and unsupported set of classes that fit the bill nicely. The atlcontrols.h file defines the following window control wrappers inside the ATLControls namespace:

CAnimateCtrl

CButton

CComboBox

CComboBoxEx

CDateTimePickerCtrl

CDragListBox

CEdit

CFlatScrollBar

CheaderCtrl

CHotKeyCtrl

CImageList

CIPAddressCtrl

ClistBox

CListViewCtrl

CMonthCalendarCtrl

CPagerCtrl

CProgressBarCtrl

CReBarCtrl

CRichEditCtrl

CScrollBar

CStatic

CStatusBarCtrl

CTabCtrl

CToolBarCtrl

CToolInfo

CToolTipCtrl

CTrackBarCtrl

CTreeItem

CTreeViewCtrl

CTreeViewCtrlEx

CupDownCtrl

For example, the CListBox class provides a set of inline wrapper functions, one per LB_XXX message. The following shows the ones that would be useful for the sample:

 1template <class Base>
 2class CListBoxT : public Base {
 3public:
 4  ...
 5  // for single-selection listboxes
 6  int GetCurSel() const
 7  { return (int)::SendMessage(m_hWnd, LB_GETCURSEL, 0, 0L); }
 8
 9  int SetCurSel(int nSelect)
10  {
11    return (int)::SendMessage(m_hWnd, LB_SETCURSEL, nSelect, 0L);
12  }
13  ...
14  // Operations
15  // manipulating listbox items
16  int AddString(LPCTSTR lpszItem)
17  { return (int)::SendMessage(m_hWnd, LB_ADDSTRING, 0, (LPARAM)lpszItem);
18}
19  ...
20  // selection helpers
21  int FindString(int nStartAfter, LPCTSTR lpszItem) const
22  { return (int)::SendMessage(m_hWnd, LB_FINDSTRING, nStartAfter,
23                              (LPARAM)lpszItem); }
24  ...
25};
26
27typedef CListBoxT<CWindow> CListBox;

Assuming a data member of type ATLControls::CListBox, the updated sample dialog code now looks much more pleasing:

 1LRESULT
 2CStringListDlg::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
 3    CenterWindow();
 4
 5    // Cache listbox HWND
 6    m_lb.Attach(GetDlgItem(IDC_LIST));
 7
 8    // Populate the listbox
 9    m_lb.AddString(__T("Hello, ATL"));
10    m_lb.AddString(__T("Ain't ATL Cool?"));
11    m_lb.AddString(__T("ATL is your friend"));
12
13    // Set initial selection
14    int n = m_lb.FindString(0, m_sz);
15    if( n == LB_ERR ) n = 0;
16    m_lb.SetCurSel(n);
17
18    return 1; // Let dialog manager set initial focus
19  }
20
21  LRESULT CStringListDlg::OnOK(WORD, UINT, HWND, BOOL&) {
22    // Copy the selected item
23    int n = m_lb.GetCurSel();
24    if( n == LB_ERR ) n = 0;
25    m_lb.GetText(n, m_sz);
26
27    EndDialog(IDOK);
28    return 0;
29  }

Because the window control wrappers are merely a collection of inline functions that call SendMessage for you, the generated code is the same. The good news, of course, is that you don’t have to pack the messages yourself.

The ATLCON sample itself hasn’t been updated since 2001, but the atlcontrols.h header still works just fine. Unfortunately, a lot of new and updated common controls have been released since then, and, of course, the header doesn’t reflect these updates. If you need updated control wrappers or want support for more sophisticated UI functions, check out the Windows Template Library (WTL). [10] WTL is a library that is written on top of and in the same style as ATL, but it provides much of the functionality that MFC does: message routing, idle time processing, convenient control wrappers, DDX/DDV support, and more.

CContainedWindow

The Parent Handling the Messages of the Child

A CWindow-based object lets an existing window class handle its messages. This is useful for wrapping child windows. A CWindowImpl-based object handles its own messages via a message map. This is handy for creating parent windows. Often, you want to let a parent window handle messages for a child window. Objects of another ATL window class, CContainedWindow, let the parent window handle the messages and let an existing window class handle the messages the parent doesn’t. This centralizes message handling in a parent window. The parent window can either create an instance of the child window class or let someone else (such as the dialog manager) create it and then subclass it later (I discuss subclassing soon). Either way, the messages of the child window are routed through the message map of the parent window. How does the parent window discern its own messages from those of one or more children? You guessed it: alternate message maps. Each CContainedWindow gets a message map ID, and its messages are routed to that alternate part of the parent’s message map.

To support the creation of both contained and subclassed windows, CContainedWindow is defined like this:

 1template <class TBase, class TWinTraits>
 2class CContainedWindowT : public Tbase {
 3public:
 4  CWndProcThunk m_thunk;
 5  LPCTSTR m_lpszClassName;
 6  WNDPROC m_pfnSuperWindowProc;
 7  CMessageMap* m_pObject;
 8  DWORD m_dwMsgMapID;
 9  const _ATL_MSG* m_pCurrentMsg;
10
11  // If you use this constructor you must supply
12  // the Window Class Name, Object* and Message Map ID
13  // Later to the Create call
14  CContainedWindowT();
15
16  CContainedWindowT(LPTSTR lpszClassName, CMessageMap* pObject,
17    DWORD dwMsgMapID = 0);
18  CContainedWindowT(CMessageMap* pObject, DWORD dwMsgMapID = 0);
19
20  void SwitchMessageMap(DWORD dwMsgMapID);
21
22  const _ATL_MSG* GetCurrentMessage() const;
23
24  LRESULT DefWindowProc();
25  LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
26
27  static LRESULT CALLBACK
28  StartWindowProc(HWND hWnd, UINT uMsg,
29    WPARAM wParam, LPARAM lParam);
30
31  static LRESULT CALLBACK
32  WindowProc(HWND hWnd, UINT uMsg,
33    WPARAM wParam, LPARAM lParam);
34
35  ATOM RegisterWndSuperclass();
36
37  HWND Create(HWND hWndParent, _U_RECT rect,
38    LPCTSTR szWindowName = NULL,
39    DWORD dwStyle = 0, DWORD dwExStyle = 0,
40    _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
41
42  HWND Create(CMessageMap* pObject, DWORD dwMsgMapID,
43    HWND hWndParent,
44    _U_RECT rect, LPCTSTR szWindowName = NULL,
45    DWORD dwStyle = 0, DWORD dwExStyle = 0,
46    _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
47
48  HWND Create(LPCTSTR lpszClassName, CMessageMap* pObject,
49    DWORD dwMsgMapID, HWND hWndParent, _U_RECT rect,
50    LPCTSTR szWindowName = NULL,
51    DWORD dwStyle = 0, DWORD dwExStyle = 0,
52    _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
53
54  BOOL SubclassWindow(HWND hWnd);
55
56    // Use only if you want to subclass before window
57    // is destroyed, WindowProc will automatically subclass
58    // when window goes away
59    HWND UnsubclassWindow(BOOL bForce = FALSE);
60
61    LRESULT ReflectNotifications(UINT uMsg, WPARAM wParam,
62    LPARAM lParam, BOOL& bHandled);
63};
64
65typedef CContainedWindowT<CWindow> CContainedWindow;

Notice that CContainedWindow does not derive from CWindowImpl, nor does it derive from CMessageMap. CContainedWindow objects do not have their own message map. Instead, the WindowProc static member function of CContainedWindow routes messages to the parent window. The specific message map ID is provided either to the constructor or to the Create function.

Creating Contained Windows

Notice also the various constructors and Create functions: Some take the name of the window class, and some do not. If you want a parent window to handle a child window’s messages, you have quite a bit of flexibility. For example, to morph the letter box example to create an edit control that accepts only letters, CContainedWindow would be used like this:

 1class CMainWindow :
 2  public CWindowImpl<CMainWindow, CWindow, CMainWindowTraits> {
 3public:
 4...
 5BEGIN_MSG_MAP(CMainWindow)
 6  ...
 7// Handle the child edit control's messages
 8ALT_MSG_MAP(1)
 9  MESSAGE_HANDLER(WM_CHAR, OnEditChar)
10END_MSG_MAP()
11
12  LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
13    // Create the contained window, routing its messages to us
14    if( m_edit.Create("edit", this, 1, m_hWnd,
15      &CWindow::rcDefault) ) {
16      return 0;
17    }
18    return -1;
19  }
20
21  // Let the child edit control receive only letters
22  LRESULT OnEditChar(UINT, WPARAM wparam, LPARAM,
23    BOOL& bHandled) {
24    if( isalpha((TCHAR)wparam) ) bHandled = FALSE;
25    else                     MessageBeep(0xFFFFFFFF);
26    return 0;
27  }
28...
29private:
30  CContainedWindow m_edit;
31};

When the main window is created, this code associates an HWND with the CContainedWindow object m_edit by creating a new edit control, identified because of the window class passed as the first parameter to Create. The second parameter is a CMessageMap pointer for where the contained windows messages will be routed. This parameter is most often the parent window, but it doesn’t have to be.

To separate the parent’s messages from the child’s, the parent’s message map is divided into two parts, the main part and one alternate part. The ID of the alternate part of the message map is passed as the third parameter to Create.

Finally, to filter all characters but the letters sent to the contained edit control, the WM_CHAR handler passes through only the WM_CHAR messages that contain letters. By setting bHandled to FALSE, the parent window indicates to the CContainedWindow WndProc that it should keep looking for a handler for this message. Eventually, after the message map has been exhausted (including any chaining that might be going on), the WindowProc passes the message onto the edit control’s normal WndProc. For nonletters, the WM_CHAR handler leaves bHandled set to trUE (the default), which stops the message from going anywhere else and stops the child edit control from seeing any WM_CHAR messages without letters in them. As far as the child edit control is concerned, the user entered only letters.

Subclassing Contained Windows

CContainedWindow, as previously described, works well if you are the one to call Create. However, in several important scenarios, you want to hook the message processing of a window that has already been createdfor example, if the dialog manager has already created an edit control. In this case, to get hold of the window’s messages, you must subclass [11] it. Previously in this chapter, I described superclassing as the Windows version of inheritance for window classes. Subclassing is a more modest and frequently used technique. Instead of creating a whole new window class, with subclassing, we merely hijack the messages of a single window. Subclassing is accomplished by creating a window of a certain class and replacing its WndProc with our own using SetWindowLong(GWL_WNDPROC). The replacement WndProc gets all the messages first and can decide whether to let the original WndProc handle it as well. If you think of superclassing as specialization of a class, subclassing is specialization of a single instance. Subclassing is usually performed on child windows, such as an edit box that the dialog wants to restrict to letters only. The dialog would subclass the child edit control during WM_INITDIALOG and handle WM_CHAR messages, throwing out any that weren’t suitable.

For example, subclassing an edit control in a dialog would look like this:

 1class CLettersDlg : public CDialogImpl<CLettersDlg> {
 2public:
 3  // Set the CMessageMap* and the message map id
 4  CLettersDlg() : m_edit(this, 1) {}
 5
 6BEGIN_MSG_MAP(CLettersDlg)
 7  ...
 8ALT_MSG_MAP(1)
 9  MESSAGE_HANDLER(WM_CHAR, OnEditChar)
10END_MSG_MAP()
11
12  enum { IDD = IDD_LETTERS_ONLY };
13
14  LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
15    // Subclass the existing child edit control
16    m_edit.SubclassWindow(GetDlgItem(IDC_EDIT));
17
18    return 1; // Let the dialog manager set the initial focus
19  }
20  ...
21private:
22  CContainedWindow m_edit;
23};

In this case, because the sample doesn’t call Create, it has to pass the CMessageMap pointer and the message map ID of the child edit control in the constructor using the member initialization list syntax. When the contained window knows who to route the messages to and which part of the message map to route to, it needs to know only the HWND of the window to subclass. It gets this in the SubclassWindow call in the WM_INITDIALOG handler. The CContainedWindow implementation of SubclassWindow shows how subclassing is performed:

 1template <class TBase, class TWinTraits>
 2BOOL CContainedWindowT<TBase, TWinTraits>::SubclassWindow(
 3    HWND hWnd) {
 4    BOOL result;
 5
 6    result = m_thunk.Init(WindowProc, this);
 7    if (result == FALSE) {
 8        return result;
 9    }
10
11    WNDPROC pProc = m_thunk.GetWNDPROC();
12    WNDPROC pfnWndProc = (WNDPROC)::SetWindowLongPtr(hWnd,
13        GWLP_WNDPROC, (LONG_PTR)pProc);
14    if(pfnWndProc == NULL)
15        return FALSE;
16    m_pfnSuperWindowProc = pfnWndProc;
17    m_hWnd = hWnd;
18    return TRUE;
19}

The important part is the call to SetWindowLong passing GWL_WNDPROC. This replaces the current window object’s WndProc with an ATL thunking version that routes messages to the container. It also returns the existing WndProc, which CContainedWindow caches to call for any messages that the container doesn’t handle.

Containing the Window Control Wrappers

After being introduced to the ATL Windows control wrapper classes, such as CEdit and CListBox, you might dread window containment. If CContainedWindow derives from CWindow, where do all the nifty inline wrappers functions come from? Never fear, ATL is here. As you’ve already seen, CContainedWindow is really just a type definition for the CContainedWindowT template class. One of the parameters is a windows traits class, which doesn’t help you. The other, however, is the name of the base class. CContainedWindow uses CWindow as the base for CContaintedWindowT, but there’s no reason you have to. By using one of the ATL Windows control wrapper classes instead, you can have a contained window that also has all the wrapper functions. For example, we can change the type of the m_edit variable like this:

1CContainedWindowT<ATLControls::CEdit> m_edit;

This technique is especially handy when you’re using Create instead of SubclassWindow. With Create, you have to provide the name of the window class. If you call Create without a window class, the CContainedWindow object attempts to acquire a window class by calling the base class function GetWndClassName, which CWindow implements like this:

1static LPCTSTR CWindow::GetWndClassName()
2{ return NULL; }

However, each of the ATL window control wrappers overrides this function to provide its window class name:

1static LPCTSTR CEditT::GetWndClassName()
2{ return _T("EDIT"); }

Now, when creating an instance of one of the contained window wrapper classes, you don’t have to dig through the documentation to figure out the class name of your favorite intrinsic window class; you can simply call Create:

 1class CMainWindow : public CWindowImpl<...> {
 2...
 3  LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
 4    // Window class name provided by base class
 5    if( m_edit.Create(this, 1, m_hWnd, &CWindow::rcDefault) ) {
 6      // Now we can use methods of CEditT
 7      m_edit.InsertText( 0, _T("Here's some text") );
 8      return 0;
 9    }
10    return -1;
11  }
12...
13private:
14  CContainedWindowT<ATLControls::CEdit> m_edit;
15};

Summary

ATL applies the same principles of efficiency and flexibility that were originally developed for COM to another part of the Windows API: User32. This support takes the form of a small set of windowing classes. CWindow, which forms the root class of the windowing hierarchy, provides a large number of inline wrapper functions for manipulating an existing HWND. To create a new window class, or to superclass an existing one and handle messages from windows of that class, ATL provides CWindowImpl. CDialogImpl provides the same functionality for dialog boxes, both modal and modeless. To subclass child windows and manage messages in the parent, ATL provides CContainedWindow. These classes can be used in standalone applications or in COM servers. For COM servers that expose COM controls, the windowing classes form the core of how input and output are managed, as discussed in the following chapter.