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 theHINSTANCE
of the application, the command-line arguments and the flag indicating how to show the main windowA call to
RegisterClass
to register the main window classA call to
CreateWindow(Ex)
to create the main windowA call to
ShowWindow
andUpdateWindow
to show the main windowA 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 inA 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

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

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.
A
window can maintain extra information via the cbWndExtra
member of the WNDCLASS[EX]
structure or via the
GetWindowLong
/SetWindowLong
family of
functions.
Instead, the ATL team used a technique based on a
set of Assembly (ASM) instructions grouped together into a
thunk, avoiding any look-up. The
term thunk is overused in Windows,
but in this case, the thunk is a group of ASM instructions that
keeps track of a CWindowImpl
object’s this
pointer and acts like a functionspecifically, a WndProc
.
Each CWindowImpl
object gets its own 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

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.

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 theory of Windows superclassing is beyond the scope of this book. For a more in-depth discussion, see Win32 Programming (Addison-Wesley, 1997), by Brent Rector and Joe Newcomer.
The reason to use superclassing is the same
reason to use inheritance of implementation: The base class has
some functionality that the deriving class wants to extend. ATL
supports superclassing via the DECLARE_WND_SUPERCLASS
macro:
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.
Available at http://wtl.sourceforge.net.
WM_COMMAND and WM_NOTIFY Messages
Of the hundreds of Windows messages, ATL
provides a bit of message-cracking assistance for two of them,
WM_COMMAND
and WM_NOTIFY
. These messages
represent how a Windows control communicates with its parent. I
should point out that Windows controls are not OLE or ActiveX
controls. A standard Windows control is a child window whose class
is defined by the Windows operating system. Some of these controls
have been with us since Windows 1.0. Classic examples of Windows
control include buttons, scrollbars, edit boxes, list boxes, and
combo boxes. With the new Windows shell introduced with Windows 95,
these controls were expanded to include toolbars, status bars, tree
views, list views, rich-text edit boxes, and more. Furthermore,
with the integration of Internet Explorer with the shell, more
Windows controls were introduced to include rebars, the date
picker, and the IP address control, for example.
Creating a Windows control is a matter of
calling CreateWindow
with the proper window class 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]
This is left as an exercise to the very capable readers of this book.
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:
The
DialogBox
function is merely a wrapper around
DialogBoxParam
, passing 0 for the dwInitParam
argument.
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:
Likewise, CreateDialog
is a wrapper around the
CreateDialogParam
function.
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:
The application creates an instance of a
CDialogImpl
-derived class.The application copies some data into the dialog object’s data members.
The application calls
DoModal
.The dialog handles
WM_INITDIALOG
by copying data members into child controls.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.
If the data is valid, the data is copied back into the dialog’s data members, and the dialog ends.
If the application gets
IDOK
fromDoModal
, 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):
The application creates an instance of a
CDialogImpl
-derived class.The application copies some data into the dialog object’s data members.
The applications calls ``Create``.
The dialog handles
WM_INITDIALOG
by copying data members into child controls.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.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.
When the application is notified, it copies the data from the dialog data members over its own copy.
A custom window message sent to the dialog’s parent is excellent for this duty.
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

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

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:
As of this writing, this sample is available online at ` http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/_sample_atl_ATLCON.asp <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/_sample_atl_ATLCON.asp>`__ (http://tinysells.com/52).
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.
Available at http://wtl.sourceforge.net.
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 a more complete dissection of Windows subclassing, see Win32 Programming (Addison-Wesley, 1997), by Brent Rector and Joe Newcomer.
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.