CWindowImpl
The Window
Class
The CWindowImpl class derives
ultimately from CWindow and provides two additional
features, window class registration and message handling. We
discuss message handling after we explore how CWindowImpl
manages the window class. First, notice that, unlike
CWindow, the CWindowImpl member function
Create doesn't take the name of a window class:
template <class T, class TBase = CWindow,
class TWinTraits = CControlWinTraits>
class ATL_NO_VTABLE CWindowImpl
: public CWindowImplBaseT< TBase, TWinTraits > {
public:
DECLARE_WND_CLASS(NULL)
HWND Create(HWND hWndParent, _U_RECT rect = NULL,
LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
...
};
Instead of passing the name of the window class,
the name of the window class is provided in the
DECLARE_WND_CLASS macro. A value of NULL causes
ATL to generate a window class of the form:
ATL<8-digit number>. We could declare a
CWindowImpl-based class using the same window class name
we registered using RegisterClass. However, that's not
necessary. It's far more convenient to let ATL register the window
class the first time we call Create on an instance of our
CWindowImpl-derived class. This initial window class
registration is done in the implementation of
CWindowImpl::Create:
HWND CWindowImpl::Create(HWND hWndParent, _U_RECT rect = NULL,
LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) {
// Generate a class name if one is not provided
if (T::GetWndClassInfo().m_lpszOrigName == NULL)
T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
// Register the window class if it hasn't
// already been registered
ATOM atom = T::GetWndClassInfo().Register(
&m_pfnSuperWindowProc);
...
}
Using a class derived from CWindowImpl,
our program has gotten much smaller. We can eliminate the
InitInstance function and place the main window creation
directly in the _tWinMain function:
class CMainWindow : public CWindowImpl<CMainWindow> {...};
// Entry point
int APIENTRY _tWinMain(HINSTANCE hinst,
HINSTANCE /*hinstPrev*/,
LPTSTR pszCmdLine,
int nCmdShow) {
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
CMainWindow wnd;
wnd.Create( 0, CWindow::rcDefault, szTitle,
WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE );
if( !wnd ) {
return FALSE;
}
wnd.CenterWindow( );
wnd.ShowWindow( nCmdShow );
wnd.UpdateWindow( );
// Show the main window, run the message loop
...
return msg.wParam;
}
We can also eliminate the
generated WndProc; the CWindowImpl class provides
message maps to handle message processing, as discussed later in
the section "Handling Messages."
Modifying the
Window Class
Each CWindowImpl class maintains a
static data structure called a CWndClassInfo, which is a
type definition for either an _ATL_WNDCLASSINFOA or an
_ATL_WNDCLASSINFOW structure, depending on whether you're
doing a Unicode build. The Unicode version is shown here:
struct _ATL_WNDCLASSINFOW {
WNDCLASSEXW m_wc;
LPCWSTR m_lpszOrigName;
WNDPROC pWndProc;
LPCWSTR m_lpszCursorID;
BOOL m_bSystemCursor;
ATOM m_atom;
WCHAR m_szAutoName[5+sizeof(void)*CHAR_BIT];
ATOM Register(WNDPROC* p)
{ return AtlModuleRegisterWndClassInfoW(&_AtlWinModule,
&_AtlBaseModule, this, p); }
};
The most important members of this structure are
m_wc and m_atom. The m_wc member
represents the window class structurethat is, what you would use to
register a class if you were doing it by hand. The m_atom
is used to determine whether the class has already been registered.
This is useful if you want to make changes to the m_wc
before the class has been registered.
Each class derived from CWindowImpl
gets an instance of CWndClassInfo in the base class from
the use of the DECLARE_WND_CLASS macro, defined like
so:
#define DECLARE_WND_CLASS(WndClassName) \
static CWndClassInfo& GetWndClassInfo() { \
static CWndClassInfo wc = { \
{ sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, \
StartWindowProc, 0, 0, NULL, NULL, NULL, \
(HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL \
}, \
NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
}; \
return wc; \
}
This macro defines a
function called GetWndClassInfo and initializes the values
to commonly used defaults. If you want to also specify the class
style and the background color, you can use another macro, called
DECLARE_WND_CLASS_EX:
#define DECLARE_WND_CLASS_EX(WndClassName, style, bkgnd) \
static CWndClassInfo& GetWndClassInfo() { \
static CWndClassInfo wc = { \
{ sizeof(WNDCLASSEX), style, StartWindowProc, \
0, 0, NULL, NULL, NULL, (HBRUSH)(bkgnd + 1), NULL, \
WndClassName, NULL \
}, \
NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
}; \
return wc; \
}
However, neither macro provides enough flexibly
to set all the window class information you want, such as large and
small icons, the cursor, or the background brush. Although it's
possible to define an entire set of macros in the same vein as
DECLARE_WND_CLASS, the combinations of what you want to
set and what you want to leave as a default value quickly get out
of hand. Frankly, it's easier to modify the CWndClassInfo
structure directly using the GetWndClassInfo function. The
CWindowImpl-derived class's constructor is a good place to
do that, using the m_atom variable to determine whether
the window class has already been registered:
CMainWindow( ) {
CWndClassInfo& wci = GetWndClassInfo();
if( !wci.m_atom ) {
wci.m_wc.lpszMenuName = MAKEINTRESOURCE(IDC_ATLHELLOWIN);
wci.m_wc.hIcon = LoadIcon(
_AtlBaseModule.GetResourceInstance(),
MAKEINTRESOURCE(IDI_ATLHELLOWIN));
wci.m_wc.hIconSm = LoadIcon(
_AtlBaseModule.GetResourceInstance(),
MAKEINTRESOURCE(IDI_SMALL));
wci.m_wc.hbrBackground = CreateHatchBrush(HS_DIAGCROSS,
RGB(0, 0, 255));
}
}
Setting the WNDCLASSEX member directly
works for most of the members of the m_wc member of
CWndClassInfo. However, the ATL team decided to treat
cursors differently. For cursors, the
CWndClassInfo structure has two members,
m_lpszCursorID and m_bSystemCursor, which are
used to override whatever is set in the hCursor member of
m_wc. For example, to set a cursor from the available
system cursors, you must do the following:
// Can't do this:
// wci.m_wc.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_CROSS));
// Must do this:
wci.m_bSystemCursor = TRUE;
wci.m_lpszCursorID = IDC_CROSS;
Likewise, to load a custom cursor, the following
is required:
// Can't do this:
// wci.m_wc.hCursor = LoadCursor(
// _AtlBaseModule.GetResourceInstance(),
// MAKEINTRESOURCE(IDC_BAREBONES));
// Must do this:
wci.m_bSystemCursor = FALSE;
wci.m_lpszCursorID = MAKEINTRESOURCE(IDC_BAREBONES);
Remember to keep this special treatment of
cursors in mind when creating CWindowImpl-derived classes
with custom cursors.
Window Traits
In the same way that an icon and a cursor are
coupled with a window class, often it makes sense for the styles
and the extended styles to be coupled as well. For example, frame
windows have different styles than child windows. When we develop a
window class, we typically know how it will be used; for example,
our CMainWindow class will be used as a frame window, and
WS_OVERLAPPEDWINDOW will be part of the styles for every
instance of CMainWindow. Unfortunately, there is no way to
set default styles for a window class in the Win32 API. Instead,
the window styles must be specified in every call to
CreateWindowEx. To allow default styles and extended
styles to be coupled with a window class, ATL enables you to group
styles and reuse them in an instance of the CWinTrait
class:
template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0>
class CWinTraits {
public:
static DWORD GetWndStyle(DWORD dwStyle)
{ return dwStyle == 0 ? t_dwStyle : dwStyle; }
static DWORD GetWndExStyle(DWORD dwExStyle)
{ return dwExStyle == 0 ? t_dwExStyle : dwExStyle; }
};
As you can see, the
CWinTrait class holds a set of styles and extended styles.
When combined with a style or an extended style DWORD, it
hands out the passed DWORD if it is nonzero; otherwise, it
hands out its own value. For example, to bundle my preferred styles
into a Windows trait, I would do the following:
typedef CWinTraits<WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE>
CMainWinTraits;
A Window traits class can be associated with a
CWindowImpl by passing it as a template parameter:
class CMainWindow : public CWindowImpl<CMainWindow,
CWindow, CMainWinTraits>
{...};
Now when creating instances of a
CWindowImpl-derived class, I can be explicit about what
parameters I want. Or, by passing zero for the style or the
extended style, I can get the Window traits style associated with
the class:
// Use the default value of 0 for the style and the
// extended style to get the window traits for this class.
wnd.Create(NULL, CWindow::rcDefault, __T("Windows Application"));
Because I've used a CWinTrait class to
group related styles and extended styles, if I need to change a
style in a trait, the change is propagated to all instances of any
class that uses that trait. This saves me from finding the
instances and manually changing them one at a time. For the three
most common kinds of windowsframe windows, child windows, and MDI
child windowsATL comes with three built-in window traits
classes:
typedef
CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS, 0>
CControlWinTraits;
typedef
CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>
CFrameWinTraits;
typedef
CWinTraits<WS_OVERLAPPEDWINDOW | WS_CHILD |
WS_VISIBLE | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS,
WS_EX_MDICHILD>
CMDIChildWinTraits;
If
you want to leverage the styles of an existing window traits class
but add styles, you can use the CWindowTraitsOR class:
template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0,
class TWinTraits = CControlWinTraits>
class CWinTraitsOR {
public:
static DWORD GetWndStyle(DWORD dwStyle) {
return dwStyle | t_dwStyle |
TWinTraits::GetWndStyle(dwStyle);
}
static DWORD GetWndExStyle(DWORD dwExStyle) {
return dwExStyle | t_dwExStyle | TWinTraits::GetWndExStyle(dwExStyle);
}
};
Using CWinTraitsOR,
CMainWinTraits can be redefined like so:
// Leave CFrameWinTraits styles alone.
// Add the WS_EX_CLIENTEDGE bit to the extended styles.
typedef CWinTraitsOR<0, WS_EX_CLIENTEDGE, CFrameWinTraits>
CMainWinTraits;
The Window
Procedure
To handle window messages, every window needs a
window procedure (WndProc). This WndProc is set
in the lpfnWndProc member of the WNDCLASSEX
structure used during window registration. You might have noticed
that in the expansion of DECLARE_WND_CLASS and
DECLARE_WND_CLASS_EX, the name of the windows procedure is
StartWindowProc. StartWindowProc is a static
member function of
CWindowImplBase. Its job is to establish the mapping
between the CWindowImpl-derived object's HWND and
the object's this pointer. The goal is to handle calls
made by Windows to a WndProc global function and map them
to a member function on an object. The mapping between
HWND and an object's this pointer is done by the
StartWindowProc when handling the first window
message. 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:
template <class TBase, class TWinTraits>
LRESULT CALLBACK
CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
CWindowImplBaseT< TBase, TWinTraits >* pThis =
(CWindowImplBaseT< TBase, TWinTraits >*)
_AtlWinModule.ExtractCreateWndData();
ATLASSERT(pThis != NULL);
pThis->m_hWnd = hWnd;
pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd,
GWLP_WNDPROC, (LONG_PTR)pProc);
return pProc(hWnd, uMsg, wParam, lParam);
}
The m_thunk member is the interesting
part. The ATL team had several different options it could have used
to map the HWND associated with each incoming window
message to the object's this pointer responsible for
handling the message. It could have kept a global table that mapped
HWNDs to this pointers, but the look-up time
would grow as the number of windows grew. It could have tucked the
this pointer into the window data, but
then the application/component developer could unwittingly
overwrite the data when doing work with window data. Plus,
empirically, this look-up is not as fast as one might like when
handling many messages per second.
Instead, the ATL team used a technique based on a
set of Assembly (ASM) instructions grouped together into a
thunk, avoiding any look-up. The
term thunk is overused in Windows,
but in this case, the thunk is a group of ASM instructions that
keeps track of a CWindowImpl object's this
pointer and acts like a functionspecifically, a WndProc.
Each CWindowImpl object gets its own thunkthat is, each
object has its own WndProc. A thunk is a set of machine
instructions built on-the-fly and executed. For example, imagine
two windows of the same class created like this:
class CMyWindow : public CWindowImpl<CMyWindow> {...};
CMyWindow wnd1; wnd1.Create(...);
CMyWindow wnd2; wnd2.Create(...);
Figure
10.3 shows the per-window class and per-HWND data
maintained by Windows on the 32-bit Intel platform, the thunks, and
the CWindowImpl objects that would result from this
example code.
The thunk's job is to replace the HWND
on the stack with the CWindowImpl object's this
pointer before calling the CWindowImpl static member
function WindowProc to further process the message. The
ASM instructions that replace the HWND with the object's
this pointer are kept in a data structure called the
_stdcallthunk. Versions of this structure are defined for
32-bit x86, AMD64, ALPHA, MIPS, SH3, ARM, and IA64 (Itanium)
processors. The 32-bit x86 definition follows:
#if defined(_M_IX86)
PVOID __stdcall __AllocStdCallThunk(VOID);
VOID __stdcall __FreeStdCallThunk(PVOID);
#pragma pack(push,1)
struct _stdcallthunk {
DWORD m_mov; // mov dword ptr [esp+0x4], pThis
// (esp+0x4 is hWnd)
DWORD m_this; // Our CWindowImpl this pointer
BYTE m_jmp; // jmp WndProc
DWORD m_relproc; // relative jmp
BOOL Init(DWORD_PTR proc, void* pThis) {
m_mov = 0x042444C7; //C7 44 24 0C
m_this = PtrToUlong(pThis);
m_jmp = 0xe9;
m_relproc = DWORD((INT_PTR)proc
((INT_PTR)this+sizeof(_stdcallthunk)));
// write block from data cache and
// flush from instruction cache
FlushInstructionCache(GetCurrentProcess(), this,
sizeof(_stdcallthunk));
return TRUE;
}
// some thunks will dynamically allocate the
// memory for the code
void* GetCodeAddress() {
return this;
}
void* operator new(size_t) {
return __AllocStdCallThunk();
}
void operator delete(void* pThunk) {
__FreeStdCallThunk(pThunk);
}
};
#pragma pack(pop)
#elif defined(_M_AMD64)
... // Other processors omitted for clarity
As you can see, this
structure initializes itself with the appropriate machine code to
implement the per-window code needed to feed the right
this pointer into the window proc. The overload of
operator new is needed to deal with the no-execute
protection feature on newer processors. The call to
__AllocStdCallThunk ensures that the thunk is
allocated on a virtual memory page that has execute permissions
instead of on the regular heap, which might not allow code
execution.
This data structure is kept per
CWindowImpl-derived object and is initialized by
StartWindowProc with the object's this pointer
and the address of the static member function used as the window
procedure. The m_thunk member that
StartWindowProc initializes and uses as the thunking
window procedure as an instance of the CWndProcThunk class
follows:
class CwndProcThunk {
public:
_AtlCreateWndData cd;
CStdCallThunk thunk;
BOOL Init(WNDPROC proc, void* pThis) {
return thunk.Init((DWORD_PTR)proc, pThis);
}
WNDPROC GetWNDPROC() {
return (WNDPROC)thunk.GetCodeAddress();
}
};
After the thunk has been set up in
StartWindowProc, each window message is routed from the
CWindowImpl object's thunk to a static member function of
CWindowImpl, to a member function of the
CWindowImpl object itself, shown in Figure 10.4.
On each Window message, the thunk removes the
HWND provided by Windows as the first argument and
replaces it with the CWindowImpl-derived object's
this pointer. The thunk then forwards the entire call
stack to the actual window procedure. Unless the virtual
GetWindowProc function is overridden, the default window
procedure is the WindowProc static function shown
here:
template <class TBase, class TWinTraits>
LRESULT CALLBACK
CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd,
UINT uMsg, WPARAM wParam, LPARAM lParam) {
CWindowImplBaseT< TBase, TWinTraits >* pThis =
(CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
// set a ptr to this message and save the old value
_ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam);
const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg;
pThis->m_pCurrentMsg = &msg;
// pass to the message map to process
LRESULT lRes;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd,
uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg);
// do the default processing if message was not handled
if(!bRet) {
if(uMsg != WM_NCDESTROY)
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
else {
// unsubclass, if needed
LONG_PTR pfnWndProc = ::GetWindowLongPtr(
pThis->m_hWnd, GWLP_WNDPROC);
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
if(pThis->m_pfnSuperWindowProc != ::DefWindowProc &&
::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC)
== pfnWndProc)
::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC,
(LONG_PTR)pThis->m_pfnSuperWindowProc);
// mark window as destroyed
pThis->m_dwState |= WINSTATE_DESTROYED;
}
}
if((pThis->m_dwState & WINSTATE_DESTROYED) && pOldMsg== NULL) {
// clear out window handle
HWND hWndThis = pThis->m_hWnd;
pThis->m_hWnd = NULL;
pThis->m_dwState &= ~WINSTATE_DESTROYED;
// clean up after window is destroyed
pThis->m_pCurrentMsg = pOldMsg;
pThis->OnFinalMessage(hWndThis);
}
else {
pThis->m_pCurrentMsg = pOldMsg;
}
return lRes;
}
The first thing WindowProc does is
extract the object's this pointer from the call stack by casting
the HWND parameter. Because this HWND parameter
has been cached in the object's m_hWnd member, no
information is lost. However, if you override
GetWindowProc and provide a custom WndProc for
use by the window thunk, remember that the HWND is a
this pointer, not an HWND.
After obtaining the object's this
pointer, WindowProc caches the current message into the
m_pCurrentMsg member of the CWindowImpl-derived
object. This message is then passed along to the
CWindowImpl-derived object's virtual member function
ProcessWindowMessage, which must be provided by the
deriving class with the following signature:
virtual BOOL
ProcessWindowMessage(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam,
LRESULT& lResult, DWORD dwMsgMapID);
This is where any message handling is to be done
by the object. For example, our CMainWindow could handle
Windows messages like this:
class CMainWindow : public CWindowImpl<CMainWindow> {
public:
virtual BOOL
ProcessWindowMessage(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam,
LRESULT& lResult, DWORD /*dwMsgMapID*/) {
BOOL bHandled = TRUE;
switch( uMsg ) {
case WM_PAINT: lResult = OnPaint(); break;
case WM_DESTROY: lResult = OnDestroy(); break;
default: bHandled = FALSE; break;
}
return bHandled;
}
private:
LRESULT OnPaint(); {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(&ps);
RECT rect; GetClientRect(&rect);
DrawText(hdc, __T("Hello, Windows"), -1, &rect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(&ps);
return 0;
}
LRESULT OnDestroy() {
PostQuitMessage(0);
return 0;
}
};
Notice that the message handlers are now member
functions instead of global functions. This makes programming a bit
more convenient. For example, inside the OnPaint handler,
BeginPaint, GetClientRect, and EndPaint
all resolve to member functions of the CMainWindow object
to which the message has been sent. Also notice that returning
FALSE from ProcessWindowMessage is all that is
required if the window message is not handled. WindowProc
handles calling DefWindowProc for unhandled messages.
As a further convenience, WindowProc
calls OnFinalMessage after the window handles the last
message and after the HWND has been zeroed out. This is
handy for shutdown when used on the application's main window. For
example, we can remove WM_DESTROY from our switch
statement and replace the OnDestroy handler with
OnFinalMessage:
virtual void CMainWindow::OnFinalMessage(HWND /*hwnd*/)
{ PostQuitMessage(0); }
As you might imagine, writing the
ProcessWindowMessage function involves a lot of
boilerplate coding, tediously mapping window messages to function
names. I show you the message map that will handle this chore for
us in the later section, "Handling Messages."
Window
Superclassing
The Windows object model of declaring a window
class and creating instances of that class is similar to that of
the C++ object model. The WNDCLASSEX structure is to an
HWND as a C++ class declaration is to a this
pointer. Extending this analogy, Windows superclassing is
like C++ inheritance. Superclassing is a technique in which the
WNDCLASSEX structure for an existing window class is
duplicated and given its own name and its own WndProc.
When a message is received for that window, it's routed to the new
WndProc. If that WndProc decides not the handle
that message fully, instead of being routed to
DefWindowProc, the message is routed to the original
WndProc. If you think of the original WndProc as
a virtual function, the superclassing window overrides the
WndProc and decides on a message-by-message basis whether
to let the base class handle the message.
The reason to use superclassing is the same
reason to use inheritance of implementation: The base class has
some functionality that the deriving class wants to extend. ATL
supports superclassing via the DECLARE_WND_SUPERCLASS
macro:
#define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) \
static ATL::CWndClassInfo& GetWndClassInfo() \
{ \
static ATL::CWndClassInfo wc = \
{ \
{ sizeof(WNDCLASSEX), 0, StartWindowProc, \
0, 0, NULL, NULL, NULL, NULL, NULL, \
WndClassName, NULL }, \
OrigWndClassName, NULL, NULL, TRUE, 0, _T("") \
}; \
return wc; \
}
The
WndClassName is the name of the deriving class's window
class. As with DECLARE_WND_CLASS[_EX], to have ATL
generate a name, use NULL for this parameter. The
OrigWndClassName parameter is the name of the existing
window class you want to "inherit" from.
For example, the existing edit control provides
the ES_NUMBER style to indicate that it should allow the
input of only numbers. If you want to provide similar functionality
but allow the input of only letters, you have two choices. First,
you can build your own edit control from scratch. Second, you can
superclass the existing edit control and handle WM_CHAR
messages like this:
// Letters-only edit control
class CLetterBox : public CWindowImpl<CLetterBox> {
public:
DECLARE_WND_SUPERCLASS(0, "EDIT")
virtual BOOL
ProcessWindowMessage(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam,
LRESULT& lResult, DWORD /*dwMsgMapID*/) {
BOOL bHandled = TRUE;
switch( uMsg ) {
case WM_CHAR:
lResult = OnChar((TCHAR)wParam, bHandled); break;
default:
bHandled = FALSE; break;
}
return bHandled;
}
private:
LRESULT OnChar(TCHAR c, BOOL& bHandled) {
if( isalpha(c) ) bHandled = FALSE;
else MessageBeep(0xFFFFFFFF);
return 0;
}
};
When an instance of
CLetterBox is created, it looks and acts just like a
built-in Windows edit control, except that it accepts only letters,
beeping otherwise.
Although superclassing is powerful, it turns out
that it is rarely used. Superclassing is useful when you need to
create more than one instance of a derived window type. More often
in Win32 development, you instead want to customize a single
window. A much more commonly used technique is known as
subclassing, which I discuss later when I present
CContainedWindow in the section titled (surprisingly
enough) "CContainedWindow."
Handling
Messages
Whether it's superclassing, subclassing, or
neither, a major part of registering a Window class is providing
the WndProc. The WndProc determines the behavior
of the window by handling the appropriate messages. You've seen how
the default WindowProc that ATL provides routes the
messages to the ProcessWindowMessage function that your
CWindowImpl-derived class provides. You've also seen how
tedious it is to route messages from the
ProcessWindowMessage function to the individual
message-handler member functions. Toward that end, ATL provides a
set of macros for building a message map that will generate an
implementation of ProcessWindowMessage for you. Providing
the skeleton of the message map are the BEGIN_MSG_MAP and
END_MSG_MAP macros, defined like this:
#define BEGIN_MSG_MAP(theClass) \
public: \
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, \
WPARAM wParam, LPARAM lParam, LRESULT& lResult, \
DWORD dwMsgMapID = 0) \
{ \
BOOL bHandled = TRUE; \
(hWnd); (uMsg); (wParam); (lParam); \
(lResult); (bHandled); \
switch(dwMsgMapID) \
{ \
case 0:
#define END_MSG_MAP() \
break; \
default: \
ATLTRACE(ATL::atlTraceWindowing, 0, \
_T("Invalid message map ID (%i)\n"), \
dwMsgMapID); \
ATLASSERT(FALSE); \
break; \
} \
return FALSE; \
}
Notice that the message map is a giant switch
statement. However, the switch is not on the message IDs
themselves, but rather on the message map ID. A single set of
message map macros can handle the messages of several windows,
typically the parent and several children. The parent window, the
window for which we're providing the message map, is identified
with the message map ID of 0. Later I discuss segregating message
handling into different sections of the same message map, resulting
in nonzero message map IDs.
Handling General
Messages
Each message that the window wants to handle
corresponds to an entry in the message map. The simplest is the
MESSAGE_HANDLER macro, which provides a handler for a
single message:
#define MESSAGE_HANDLER(msg, func) \
if(uMsg == msg) { \
bHandled = TRUE; \
lResult = func(uMsg, wParam, lParam, bHandled); \
if(bHandled) return TRUE; \
}
If you want to use a single message handler for
a range of Windows messages, you can use the
MESSAGE_RANGE_HANDLER macro:
#define MESSAGE_RANGE_HANDLER(msgFirst, msgLast, func) \
if(uMsg >= msgFirst && uMsg <= msgLast) { \
bHandled = TRUE; \
lResult = func(uMsg, wParam, lParam, bHandled); \
if(bHandled) return TRUE; \
}
Using the message map macros, we can replace the
sample implementation of ProcessWindowMessage with the
following message map:
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
END_MSG_MAP()
This expands roughly to
the following implementation of ProcessWindow-Message:
// BEGIN_MSG_MAP(CMainWindow)
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, LRESULT& lResult,
DWORD dwMsgMapID = 0) {
BOOL bHandled = TRUE;
switch (dwMsgMapID) {
case 0:
// MESSAGE_HANDLER(WM_PAINT, OnPaint)
if(uMsg == WM_PAINT) {
bHandled = TRUE;
lResult = OnPaint(uMsg, wParam, lParam, bHandled);
if (bHandled) return TRUE;
}
// END_MSG_MAP()
break;
default:
ATLTRACE2(atlTraceWindowing, 0,
_T("Invalid message map ID (%i)\n"),
dwMsgMapID);
ATLASSERT(FALSE);
break;
}
return FALSE;
}
Note two points here. First, if there is an
entry in the message map, that message is assumed to be handledthat
is, the default window procedure is not called for that message.
However, the BOOL& bHandled is provided to each
message handler, so you can change it to FALSE in the
message handler if you want ATL to keep looking for a handler for
this message. If you are subclassing or superclassing, the original
window procedure receives the message only when bHandled
is set to FALSE. Also, it's possible that another message
handler further down the map will receive the message. This is
useful for message map chaining, which I discuss in the section
"Message Chaining." Ultimately, if nobody is interested in the
message, DefWindowProc gets it.
The
second interesting note in this generated code is the member
function signature required to handle a message map entry. All
general messages are passed to a message handler with the following
signature:
LRESULT
MessageHandler(UINT nMsg, WPARAM wparam, LPARAM lparam,
BOOL& bHandled);
You can either add the entry and the member
function by hand, or, in Class view, you can right-click any class
with a message map, choose Properties, and click the Messages
button at the top of the property window. This gives you a list of
the messages that you can handle, and, if you enter a function name
in one of the slots, Visual Studio adds a MessageHandler macro to
the map, a declaration to the .h file, and a stub to the
.cpp file (as described in Chapter 1, ""Hello, ATL). Unfortunately,
however you add the handler, you're still responsible for
cracking your own messages. When
you crack a message, you pull the data appropriate for the specific
message from the wParam and lParam arguments
passed to the message-handler method. For example, if you want to
extract the coordinates of a mouse click, you must manually unpack
the x and y positions from the lParam argument:
LRESULT CMainWindow::OnLButtonDown(UINT nMsg, WPARAM wParam,
LPARAM lParam, BOOL &bHandled) {
int xPos = GET_X_LPARAM(lParam);
int yPos = GET_Y_LPARAM(lParam);
...
return 0;
}
At last count, there were more than 300 standard
messages, each with their own interpretation of WPARAM and
LPARAM, so this can be quite a job. Luckily, the Windows
Template Library (WTL) add-on library to ATL, mentioned
later in this chapter, provides this and more.
WM_COMMAND and
WM_NOTIFY Messages
Of the hundreds of Windows messages, ATL
provides a bit of message-cracking assistance for two of them,
WM_COMMAND and WM_NOTIFY. These messages
represent how a Windows control communicates with its parent. I
should point out that Windows controls are not OLE or ActiveX
controls. A standard Windows control is a child window whose class
is defined by the Windows operating system. Some of these controls
have been with us since Windows 1.0. Classic examples of Windows
control include buttons, scrollbars, edit boxes, list boxes, and
combo boxes. With the new Windows shell introduced with Windows 95,
these controls were expanded to include toolbars, status bars, tree
views, list views, rich-text edit boxes, and more. Furthermore,
with the integration of Internet Explorer with the shell, more
Windows controls were introduced to include rebars, the date
picker, and the IP address control, for example.
Creating a Windows control is a matter of
calling CreateWindow with the proper window class namefor
example, EDITjust like creating any other kind of window.
Communicating from the parent to a child Windows controls is a
matter of calling SendMessage with the appropriate
parameters, as in EM_GETSEL to get the currently selected
text in a child EDIT control. Communicating from a child
window to its parent also works via SendMessage, most
often using the WM_COMMAND or WM_NOTIFY messages.
These messages provide enough information packed into
WPARAM and LPARAM to describe the event of which
the control is notifying the parent, as shown here:
WM_COMMAND
wNotifyCode = HIWORD(wParam); // notification code
wID = LOWORD(wParam); // item, control, or
// accelerator identifier
hwndCtl = (HWND)lParam; // handle of control
WM_NOTIFY
idCtrl = (int)wParam; // control identifier
pnmh = (LPNMHDR)lParam; // address of NMHDR structure
Notice that the WM_NOTIFY message is
accompanied by a pointer to a NMHDR, which is defined like
this:
typedef struct tagNMHDR {
HWND hwndFrom; // handle of the control
UINT idFrom; // control identifier
UINT code; // notification code
} NMHDR;
For example, an edit box notifies the parent of
a change in the text with a WM_COMMAND message using the
EN_CHANGE notification code. The parent might or might not
want to handle this particular message. If it does, it wants to
avoid the responsibility of breaking out the individual parts of
the command notification. ATL provides several macros for splitting
the parts of WM_COMMAND and WM_NOTIFY messages.
All these macros assume the following handler-function
signatures:
LRESULT
CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl,
BOOL& bHandled);
LRESULT
NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);
The most basic handler macros are
COMMAND_HANDLER and NOTIFY_HANDLER:
#define COMMAND_HANDLER(id, code, func) \
if(uMsg == WM_COMMAND && \
id == LOWORD(wParam) && \
code == HIWORD(wParam)) \
{ \
bHandled = TRUE; \
lResult = func(HIWORD(wParam), LOWORD(wParam), \
(HWND)lParam, bHandled); \
if(bHandled) return TRUE; \
}
#define NOTIFY_HANDLER(id, code, func) \
if(uMsg == WM_NOTIFY && \
id == ((LPNMHDR)lParam)->idFrom && \
code == ((LPNMHDR)lParam)->code) \
{ \
bHandled = TRUE; \
lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
if(bHandled) return TRUE; \
}
These basic handler macros let you specify both
the id of the control and the command/notification code
that the control is sending. Using these macros, handling an
EN_CHANGE notification from an edit control looks like
this:
COMMAND_HANDLER(IDC_EDIT1, EN_CHANGE, OnEdit1Change)
Likewise, handling a TBN_BEGINDRAG
notification from a toolbar control looks like this:
NOTIFY_HANDLER(IDC_TOOLBAR1, TBN_BEGINDRAG, OnToolbar1BeginDrag)
As an example, let's add a menu bar to the
sample Windows application:
int APIENTRY _tWinMain(HINSTANCE hinst,
HINSTANCE /*hinstPrev*/,
LPTSTR pszCmdLine,
int nCmdShow) {
// Initialize the ATL module
...
// Create the main window
CMainWindow wnd;
// Load a menu
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDR_MENU));
// Use the value 0 for the style and the extended style
// to get the window traits for this class.
wnd.Create(0, CWindow::rcDefault, __T("Windows Application"),
0, 0, (UINT)hMenu);
if( !wnd ) return -1;
... // The rest is the same
}
Assuming standard File, Exit and Help, About
items in our menu, handling the menu item selections looks like
this:
class CMainWindow : public CWindowImpl<CMainWindow,
CWindow, CMainWinTraits> {
public:
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
COMMAND_HANDLER(ID_FILE_EXIT, 0, OnFileExit)
COMMAND_HANDLER(ID_HELP_ABOUT, 0, OnHelpAbout)
END_MSG_MAP()
...
LRESULT OnFileExit(WORD wNotifyCode, WORD wID, HWND hWndCtl,
BOOL& bHandled);
LRESULT OnHelpAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl,
BOOL& bHandled);
};
You might notice that menus are a little
different from most Windows controls. Instead of using the ID of a
child window as the first parameter, such as for an edit control, we use the ID of the menu item; the
command code itself is unused. In general, it's not uncommon for
the ID or the code to be unimportant when routing a message to a
handler. You've already seen one example: Handling a menu item
doesn't require checking the code. Another example of when you
don't need to worry about the code is if you want to route all
events for one control to a single handler. Because the code is
provided as an argument to the handler, further decisions can be
made about how to handle a specific code for a control. To route
events without regard for the specific code, ATL provides
COMMAND_ID_HANDLER and NOTIFY_ID_HANDLER:
#define COMMAND_ID_HANDLER(id, func) \
if(uMsg == WM_COMMAND && id == LOWORD(wParam)) \
{ \
bHandled = TRUE; \
lResult = func(HIWORD(wParam), LOWORD(wParam), \
(HWND)lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
#define NOTIFY_ID_HANDLER(id, func) \
if(uMsg == WM_NOTIFY && id == ((LPNMHDR)lParam)->idFrom) \
{ \
bHandled = TRUE; \
lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
Using COMMAND_ID_HANDLER, our menu
routing would more conventionally be written this way:
COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
Furthermore, if you want to route notifications
for a range of controls, ATL provides COMMAND_RANGE_HANDLER and
NOTIFY_RANGE_HANDLER:
#define COMMAND_RANGE_HANDLER(idFirst, idLast, func) \
if(uMsg == WM_COMMAND && LOWORD(wParam) >= idFirst && \
LOWORD(wParam) <= idLast) \
{ \
bHandled = TRUE; \
lResult = func(HIWORD(wParam), LOWORD(wParam), \
(HWND)lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
#define NOTIFY_RANGE_HANDLER(idFirst, idLast, func) \
if(uMsg == WM_NOTIFY && \
((LPNMHDR)lParam)->idFrom >= idFirst && \
((LPNMHDR)lParam)->idFrom <= idLast) \
{ \
bHandled = TRUE; \
lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
It's also possible that you want to route messages
without regard for their ID. This is useful if you want to use a
single handler for multiple controls. ATL supports this use with
COMMAND_CODE_HANDLER and NOTIFY_CODE_HANDLER:
#define COMMAND_CODE_HANDLER(code, func) \
if(uMsg == WM_COMMAND && code == HIWORD(wParam)) \
{ \
bHandled = TRUE; \
lResult = func(HIWORD(wParam), LOWORD(wParam), \
(HWND)lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
#define NOTIFY_CODE_HANDLER(cd, func) \
if(uMsg == WM_NOTIFY && cd == ((LPNMHDR)lParam)->code) \
{ \
bHandled = TRUE; \
lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
Again, because the ID of the control is
available as a parameter to the handler, you can make further
decisions based on which control is sending the notification
code.
Why
WM_NOTIFY?
As
an aside, you might be wondering why we have both
WM_COMMAND and WM_NOTIFY. After all,
WM_COMMAND alone sufficed for Windows 1.0 through Windows
3.x. However, when the new shell
team was building the new controls, team members really wanted to
send along more information than just the ID of the control and the
notification code. Unfortunately, all the bits of both
WPARAM and LPARAM were already being used in
WM_COMMAND, so the shell team invented a new message so
that they could send a pointer to a structure as the
LPARAM, keeping the ID of the control in the
WPARAM (as in WM_NOTIFY). However, if you examine
the definition of the NMHDR structure, you'll notice that
there is no more information than was available in
WM_COMMAND. Actually, there is a difference. Depending on
the type of control that is sending the message, the
LPARAM could point to something else that has the same
layout as an NMHDR but that has extra information tacked
onto the end. For example, if you receive a
TBN_BEGIN_DRAG, the NMHDR pointer actually points
to an NMTOOLBAR structure:
typedef struct tagNMTOOLBAR {
NMHDR hdr;
int iItem;
TBBUTTON tbButton;
int cchText;
LPTSTR pszText;
} NMTOOLBAR, FAR* LPNMTOOLBAR;
Because the first member of the
NMTOOLBAR structure is an NMDHR, it's safe to
cast the LPARAM to an NMHDR, even though it
actually points at an NMTOOLBAR. If you want, you can
consider this "inheritance" for C programmers. . ..
Message Reflection
and Forwarding
In many cases, Windows controls send messages to
their parent windows: when a button is clicked, when a treeview
item is expanded, or when a list-box item is selected, for example.
These messages are usually the result of user action, and the
parent window is often the best place to handle the user's
request.
Other messages are also sent to the parent
window. These messages are sent not as the result of a user's
action, but as a way for the parent window to customize something
about the control's operation. The classic example is the
WM_CTLCOLORXXX set of messages, which are sent to the
parent window to allow it to change the default colors when a
control draws. Handling such messages is fairly easy. For example,
imagine that I want to display a static window with red text on a
green background:
class CMainWindow : public CWindowImpl< CMainWindow, CWindow,
CMainWinTraits >
{
public:
CMainWindow( ) {
}
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnControlColorStatic)
END_MSG_MAP()
private:
LRESULT OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam,
BOOL& bHandled) {
m_staticBackground = ::CreateSolidBrush(RGB(0, 255, 0));
RECT rc;
rc.left = 25;
rc.right = 300;
rc.top = 25;
rc.bottom = 75;
if( m_message.Create(_T("static"),
m_hWnd, rc, _T("Static Message"),
WS_VISIBLE | WS_CHILD ) ) {
return 0;
}
return -1;
}
LRESULT OnControlColorStatic(UINT nMsg,
WPARAM wParam, LPARAM lParam,
BOOL& bHandled) {
HDC hdc = reinterpret_cast< HDC >( wParam );
::SetTextColor(hdc, RGB(255, 0, 0) );
::SetBkColor( hdc, RGB(0, 255, 0 ) );
return reinterpret_cast< LRESULT >(m_staticBackground);
}
void OnFinalMessage(HWND hWnd) {
::DeleteObject( m_staticBackground );
PostQuitMessage(0);
}
CWindow m_message;
HBRUSH m_staticBackground;
};
This code is fairly
straightforward. At creation, we create a brush to hand to the
static control when it paints. When we receive the
WM_CTLCOLORSTATIC, we set up the provided HDC to
draw in the proper colors. OnFinalMessage cleans up the
brush we created so that we can be nice Windows citizens.
This implementation works well . . . until you
add a second static control. Then, you need to figure out which
control is drawing and set its colors appropriately in the
OnControlColorStatic method. Or what if you want custom
colors for a second window class? Now, we have to start copying and
pasting. Ideally, we want to build a single CColoredStatic
window class and let it handle all the details.
The only tricky part about writing that class is
that the underlying static control is hard-wired to send the
WM_CTLCOLORSTATIC message only to its parent window. Somehow in the
implementation of CColoredStatic, we need to hook the
message map of our parent window, preferably without having to warp
the implementation of the parent window class to support our new
child control.
The traditional Windows approach to this problem
is to have your control subclass its parent window. This can be
done, but it is a little tricky to get right. ATL has introduced a
new feature called notification
reflection that lets us easily get these parent
notifications back to the original control.
In the parent message map, you need to add the
REFLECT_NOTIFICATIONS() macro. This macro is defined as
follows:
#define REFLECT_NOTIFICATIONS() \
{ \
bHandled = TRUE; \
lResult = ReflectNotifications(uMsg, \
wParam, lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
This passes the message on to the
ReflectNotifications method, which is defined in
CWindowImplRoot:
template <class TBase>
LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg,
WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HWND hWndChild = NULL;
switch(uMsg) {
case WM_COMMAND:
if(lParam != NULL) // not from a menu
hWndChild = (HWND)lParam;
break;
case WM_NOTIFY:
hWndChild = ((LPNMHDR)lParam)->hwndFrom;
break;
case WM_PARENTNOTIFY:
switch(LOWORD(wParam)) {
case WM_CREATE:
case WM_DESTROY:
hWndChild = (HWND)lParam;
break;
default:
hWndChild = GetDlgItem(HIWORD(wParam));
break;
}
break;
case WM_DRAWITEM:
if(wParam) // not from a menu
hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;
break;
case WM_MEASUREITEM:
if(wParam) // not from a menu
hWndChild = GetDlgItem(
((LPMEASUREITEMSTRUCT)lParam)->CtlID);
break;
case WM_COMPAREITEM:
if(wParam) // not from a menu
hWndChild = ((LPCOMPAREITEMSTRUCT)lParam)->hwndItem;
break;
case WM_DELETEITEM:
if(wParam) // not from a menu
hWndChild = ((LPDELETEITEMSTRUCT)lParam)->hwndItem;
break;
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
case WM_HSCROLL:
case WM_VSCROLL:
hWndChild = (HWND)lParam;
break;
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLORSTATIC:
hWndChild = (HWND)lParam;
break;
default:
break;
}
if(hWndChild == NULL) {
bHandled = FALSE;
return 1;
}
ATLASSERT(::IsWindow(hWndChild));
return ::SendMessage(hWndChild, OCM__BASE + uMsg,
wParam, lParam);
}
This function simply looks for all the standard
parent notifications and, if found, forwards the message back to
the control that sent it. One thing to notice is that the message
ID is changed by adding OCM_BASE to the message code. A
set of macros is defined for forwarded messages that begin with
OCM_ instead of WM_. The message values are
changed so that the child control can tell that the message is a
reflection back from its parent instead of a notification from one
of its own children.
For our self-colorizing static control, we need
to handle OCM_CTLCOLORSTATIC as follows:
class CColoredStatic : public CWindowImpl< CColoredStatic > {
public:
DECLARE_WND_SUPERCLASS(0, _T("STATIC"))
CColoredStatic( COLORREF foreground, COLORREF background ) {
m_foreground = foreground;
m_background = background;
m_backgroundBrush = ::CreateSolidBrush( m_background );
}
~CColoredStatic( ) {
::DeleteObject( m_backgroundBrush );
}
private:
BEGIN_MSG_MAP(CColoredStatic)
MESSAGE_HANDLER(OCM_CTLCOLORSTATIC, OnControlColorStatic)
END_MSG_MAP()
LRESULT OnControlColorStatic(UINT nMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled) {
HDC hdc = reinterpret_cast< HDC >( wParam );
::SetTextColor(hdc, m_foreground );
::SetBkColor( hdc, m_background );
return reinterpret_cast< LRESULT >(m_backgroundBrush);
}
COLORREF m_foreground;
COLORREF m_background;
HBRUSH m_backgroundBrush;
};
Our
parent window class is simplified as well:
class CMainWindow :
public CWindowImpl< CMainWindow, CWindow, CMainWinTraits >
{
public:
CMainWindow( ) :
m_message( RGB( 0, 0, 255 ), RGB(255, 255, 255) ) {
}
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
...
};
A set of macros for use in the child control
message map corresponds to the regular ones that handle command and
notification messages: REFLECTED_COMMAND_HANDLER,
REFLECTED_COMMAND_ID_HANDLER,
REFLECTED_NOTIFY_HANDLER, and so on. In addition,
DEFAULT_REFLECTION_HANDLER sends the reflected message
down to DefWindowProc.
There's one minor nagging nit with the
message-reflection setup: We still need to modify our parent window
class. It sure would be nice to avoid this, especially when
building reusable controls. Luckily, the ATL authors thought of
this with the CWindowWithReflectorImpl class. Using this
class is quite easy: Simply replace your CWindowImpl base
class with CWindowWithReflectorImpl:
class CColoredStatic :
public CWindowWithReflectorImpl< CColoredStatic >
{
//
... the rest is exactly the same ...
};
You can now remove the
REFLECT_NOTIFICATIONS() macro from the parent's message
map, and messages will still be routed as before.
The implementation of
CWindowWithReflectorImpl is quite small:
template <class T, class TBase, class TwinTraits >
class ATL_NO_VTABLE CWindowWithReflectorImpl :
public CWindowImpl< T, TBase, TWinTraits > {
public:
HWND Create(HWND hWndParent, _U_RECT rect = NULL,
LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) {
m_wndReflector.Create(hWndParent, rect, NULL,
WS_VISIBLE | WS_CHILD |
WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0,
Reflector::REFLECTOR_MAP_ID);
RECT rcPos = { 0, 0, rect.m_lpRect->right,
rect.m_lpRect->bottom };
return CWindowImpl<
T, TBase, TWinTraits >::Create(m_wndReflector,
rcPos, szWindowName, dwStyle, dwExStyle,
MenuOrID, lpCreateParam);
}
typedef CWindowWithReflectorImpl<
T, TBase, TWinTraits > thisClass;
BEGIN_MSG_MAP(thisClass)
MESSAGE_HANDLER(WM_NCDESTROY, OnNcDestroy)
MESSAGE_HANDLER(WM_WINDOWPOSCHANGING,
OnWindowPosChanging)
END_MSG_MAP()
LRESULT OnNcDestroy(UINT, WPARAM, LPARAM, BOOL& bHandled) {
m_wndReflector.DestroyWindow();
bHandled = FALSE;
return 1;
}
LRESULT OnWindowPosChanging(UINT uMsg,
WPARAM wParam, LPARAM lParam,
BOOL& ) {
WINDOWPOS* pWP = (WINDOWPOS*)lParam;
m_wndReflector.SetWindowPos(m_wndReflector.GetParent(),
pWP->x, pWP->y, pWP->cx, pWP->cy, pWP->flags);
pWP->flags |= SWP_NOMOVE;
pWP->x = 0;
pWP->y = 0;
return DefWindowProc(uMsg, wParam, lParam);
}
// reflector window stuff
class Reflector : public CWindowImpl<Reflector> {
public:
enum { REFLECTOR_MAP_ID = 69 };
DECLARE_WND_CLASS_EX(_T("ATLReflectorWindow"), 0, -1)
BEGIN_MSG_MAP(Reflector)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
} m_wndReflector;
};
This class actually creates two windows. The inner window is your actual
window. The outer window is an invisible parent window that does
nothing but reflect parent notifications back to the control. This
extra parent window includes the REFLECT_NOTIFICATIONS
macro in its message maps so you don't have to.
The best thing about this implementation is the
transparency. CWindowWithReflectorImpl::Create returns the
HWND of the inner control, not the invisible outer window.
The parent can send messages to the control directly and doesn't
have to know anything about the outer invisible window.
Message
Forwarding
Just as some messages are inconveniently
hard-wired to go to a parent window, sometimes messages are
hard-wired to go to a window, but we'd rather let the parent handle
it. This can be especially useful if you're building a composite
control: a single control that contains multiple child controls.
The notifications will go up to the parent control, but we might
want to pass them to the composite control's parent easily.
To do this, add the
FORWARD_NOTIFICATIONS() macro to your control's message
map.
#define FORWARD_NOTIFICATIONS() { \
bHandled = TRUE; \
lResult = ForwardNotifications(uMsg, \
wParam, lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
This macro calls the
ForwardNotifications function defined in
CWindowImplRoot:
template <class TBase>
LRESULT
CWindowImplRoot< TBase >::ForwardNotifications(UINT uMsg,
WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
LRESULT lResult = 0;
switch(uMsg) {
case WM_COMMAND:
case WM_NOTIFY:
case WM_PARENTNOTIFY:
case WM_DRAWITEM:
case WM_MEASUREITEM:
case WM_COMPAREITEM:
case WM_DELETEITEM:
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
case WM_HSCROLL:
case WM_VSCROLL:
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLORSTATIC:
lResult = GetParent().SendMessage(uMsg, wParam, lParam);
break;
default:
bHandled = FALSE;
break;
}
return lResult;
}
The
implementation is very simple; it forwards the message straight up
to the parent window. There is no change in the message ID, so the
parent window can use a normal WM_ message ID in its
message map.
Between message reflection and forwarding, it's
quite easy to shuffle standard notifications up and down the
windowing tree as needed.
Message
Chaining
If you find yourself handling messages in the
same way, you might want to reuse the message-handler
implementations. If you're willing to populate the message map
entries yourself, there's no reason you can't use normal C++
implementation techniques:
template <typename Deriving>
class CFileHandler {
public:
LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
};
class CMainWindow :
public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,
public CFileHandler<CMainWindow>
{
public:
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
// Route messages to base class
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
END_MSG_MAP()
...
};
This technique is somewhat cumbersome, however.
If the base class gained a new message handler, such as
OnFileClose, each deriving class would have to manually
add an entry to its message map. We'd really like the capability to
"inherit" a base class's message map as well as a base class's
functionality. For this, ATL provides message chaining.
Simple Message
Chaining
Message chaining is the capability to extend a
class's message map by including the message map of a base class or
another object altogether. The simplest macro of the message
chaining family is CHAIN_MSG_MAP:
#define CHAIN_MSG_MAP(theChainClass) { \
if (theChainClass::ProcessWindowMessage(hWnd, uMsg, \
wParam, lParam, lResult)) \
return TRUE; \
}
This macro allows chaining to the message map of
a base class:
template <typename Deriving>
class CFileHandler {
public:
// Message map in base class
BEGIN_MSG_MAP(CMainWindow)
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
END_MSG_MAP()
LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
};
class CMainWindow :
public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,
public CFileHandler<CMainWindow>
{
public:
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
// Chain to a base class
CHAIN_MSG_MAP(CFileHandler<CMainWindow>)
END_MSG_MAP()
...
};
Any base class that provides its own
implementation of Process-WindowMessagefor example, with
the message map macroscan be used as a chainee. Also notice that
CFileHandler is parameterized by the name of the deriving
class. This is useful when used with static cast to obtain a
pointer to the more derived class. For example, when implementing
OnFileExit, you need to destroy the window represented by
the deriving class:
template <typename Deriving>
LRESULT CFileHandler<Deriving>::OnFileExit(
WORD, WORD, HWND, BOOL&) {
static_cast<Deriving*>(this)->DestroyWindow();
return 0;
}
Message chaining to a base class can be extended
for any number of base classes. For example, if you wanted to
handle the File, Edit, and Help menus in separate base classes, you
would have several chain entries:
class CMainWindow :
public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,
public CFileHandler<CMainWindow>,
public CEditHandler<CMainWinow>,
public CHelpHandler<CMainWindow>
{
public:
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
// Chain to a base class
CHAIN_MSG_MAP(CFileHandler<CMainWindow>)
CHAIN_MSG_MAP(CEditHandler<CMainWindow>)
CHAIN_MSG_MAP(CHelpHandler<CMainWindow>)
END_MSG_MAP()
...
};
If instead of chaining to a base class message map
you want to chain to the message map of a data member, you can use
the CHAIN_MSG_MAP_MEMBER macro:
#define CHAIN_MSG_MAP_MEMBER(theChainMember) { \
if (theChainMember.ProcessWindowMessage(hWnd, uMsg, \
wParam, lParam, lResult)) \
return TRUE; \
}
If a handler will be a data member, it needs to
access a pointer to the actual object differently; a static cast
won't work. For example, an updated CFileHandler takes a
pointer to the window for which it's handling messages in the
constructor:
template <typename TWindow>
class CFileHandler {
public:
BEGIN_MSG_MAP(CFileHandler)
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
END_MSG_MAP()
CFileHandler(TWindow* pwnd) : m_pwnd(pwnd) {}
LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
private:
TWindow* m_pwnd;
};
An updated implementation would use the cached
pointer to access the window instead of a static cast:
template <typename TWindow>
LRESULT CFileHandler<TWindow>::OnFileExit(WORD, WORD wID,
HWND, BOOL&) {
m_pwnd->DestroyWindow();
return 0;
}
When we have an updated
handler class, using it looks like this:
class CMainWindow :
public CWindowImpl<CMainWindow, CWindow, CMainWinTraits> {
public:
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
// Chain to the CFileHandler member
CHAIN_MSG_MAP_MEMBER(m_handlerFile)
END_MSG_MAP()
...
private:
CFileHandler<CMainWindow> m_handlerFile;
};
Alternate Message
Maps
It's possible to break a message map into
multiple pieces. Each piece is called an alternate message map. Recall that the message
map macros expand into a switch statement that switches on the
dwMsgMapID parameter to
ProcessWindowMessage. The main part of the
message map is the first part and is identified with a zero message
map ID. An alternate part of the message map, on the other hand, is
distinguished with a nonzero message map ID. As each message comes
in, it's routed first by message map ID and then by message. When a
window receives its own messages and those messages chained from
another window, an alternate part of the message map allows the
window to distinguish where the messages are coming from. A message
map is broken into multiple parts using the ALT_MSG_MAP
macro:
#define ALT_MSG_MAP(msgMapID) \
break; \
case msgMapID:
For example, imagine a child window that is
receiving messages routed to it from the main window:
class CView : public CWindowImpl<CView> {
public:
BEGIN_MSG_MAP(CView)
// Handle CView messages
MESSAGE_HANDLER(WM_PAINT, OnPaint)
// Handle messages chained from the parent window
ALT_MSG_MAP(1)
COMMAND_HANDLER(ID_EDIT_COPY, OnCopy)
END_MSG_MAP()
...
};
Because the message map has been split, the child
window (CView) receives only its own messages in the main
part of the message map. However, if the main window were to chain
messages using CHAIN_MSG_MAP_MEMBER, the child would
receive the messages in the main part of the message map, not the
alternate part. To chain messages to an alternate part of the
message map, ATL provides two macros, CHAIN_MSG_MAP_ALT
and CHAIN_MSG_MAP_ALT_MEMBER:
#define CHAIN_MSG_MAP_ALT(theChainClass, msgMapID) { \
if (theChainClass::ProcessWindowMessage(hWnd, uMsg, \
wParam, lParam, lResult, msgMapID)) \
return TRUE; \
}
#define CHAIN_MSG_MAP_ALT_MEMBER(theChainMember, msgMapID) { \
if (theChainMember.ProcessWindowMessage(hWnd, uMsg, \
wParam, lParam, lResult, msgMapID)) \
return TRUE; \
}
For example, for the parent window to route
unhandled messages to the child, it can use
CHAIN_MSG_ALT_MEMBER like this:
class CMainWindow : ... {
public:
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
...
// Route unhandled messages to the child window
CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1)
END_MSG_MAP()
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
return m_view.Create(m_hWnd, CWindow::rcDefault) ? 0 : -1;
}
...
private:
CView m_view;
};
Dynamic
Chaining
Message map chaining to a base class or a member
variable is useful but not as flexible as you might like. What if
you want a looser coupling between the window that sent the message
and the handler of the message? For example, the MFC
WM_COMMAND message routing depends on just such a loose
coupling. The View receives all the WM_COMMAND messages
initially, but the Document handles file-related command messages.
If we want to construct such a relationship using ATL, we have one
more chaining message map macro,
CHAIN_MSG_MAP_DYNAMIC:
#define CHAIN_MSG_MAP_DYNAMIC(dynaChainID) { \
if (CDynamicChain::CallChain(dynaChainID, hWnd, \
uMsg, wParam, lParam, lResult)) \
return TRUE; \
}
Chaining sets up a relationship between two
objects that handle messages. If the object that first receives the
message doesn't handle it, the second object in line can handle it.
The relationship is established using a dynamic chain ID. A dynamic chain ID is a
number that the primary message processor uses to identify the
secondary message processor that wants to process unhandled
messages. To establish the dynamic chaining relationship, two
things must happen. First, the secondary message processor must
derive from CMessageMap:
class ATL_NO_VTABLE CMessageMap {
public:
virtual BOOL
ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID) = 0;
};
CMessageMap is actually a poorly named
class. A better name would be something like
CMessageHandler or even CMessageProcessor. All
that CMessageMap does is guarantee that every class that
derives from it will implement ProcessWindowMessage. In
fact, CWindowImpl derives from it as well, making an
implementation of ProcessWindowMessage mandatory for
CWindowImpl-derived classes. A secondary message processor
must derive from CMessageMap so that it can be placed in
the ATL_CHAIN_ENTRY structure managed by the primary
message processor:
struct ATL_CHAIN_ENTRY {
DWORD m_dwChainID;
CMessageMap* m_pObject;
DWORD m_dwMsgMapID;
};
A primary message processor
that wants to chain messages dynamically derives from
CDynamicChain, a base class that manages a dynamic array
of ATL_CHAIN_ENTRY structures. CDynamicChain
provides two important member functions. The first,
SetChainEntry, is used to add an ATL_CHAIN_ENTRY
structure to the list:
BOOL
CDynamicChain::SetChainEntry(DWORD dwChainID, CMessageMap* pObject,
DWORD dwMsgMapID = 0);
The other important function,
CallChain, is used by the CHAIN_MSG_MAP_DYNAMIC
macro to chain messages to any interested parties:
BOOL
CDynamicChain::CallChain(DWORD dwChainID, HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam, LRESULT& lResult);
As an example of this technique, imagine an
application built using a simplified Document/View architecture.
The main window acts as the frame, holding the menu bar and two
other objects, a document and a view. The view manages the client
area of the main window and handles the painting of the data
maintained by the document. The view is also responsible for
handling view-related menu commands, such as Edit | Copy. The
document is responsible for maintaining the current state as well
as handling document-related menu commands, such as File | Save. To
route command messages to the view, the main window uses an
alternate message map, member function chaining (which we've
already seen). However, to continue routing commands from the view
to the document, after creating both the document and the view, the
main window "hooks" them together using SetChainEntry. Any
messages unhandled by the view automatically are routed to the
document by the CHAIN_MSG_MAP_DYNAMIC entry in the view's
message map. Finally, the document handles any messages it likes,
leaving unhandled messages for DefWindowProc.
The main window creates the document and view,
and hooks them together:
class CMainWindow :
public CWindowImpl<CMainWindow, CWindow, CMainWinTraits> {
public:
BEGIN_MSG_MAP(CMainWindow)
// Handle main window messages
MESSAGE_HANDLER(WM_CREATE, OnCreate)
...
// Route unhandled messages to the view
CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1)
// Pick up messages the view hasn't handled
COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
END_MSG_MAP()
CMainWindow() : m_doc(this), m_view(&m_doc) {
// Hook up the document to receive messages from the view
m_view.SetChainEntry(1, &m_doc, 1);
}
private:
// Create the view to handle the main window's client area
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
{
return (m_view.Create(m_hWnd, CWindow::rcDefault) ? 0 : -1);
}
LRESULT OnHelpAbout(WORD, WORD, HWND, BOOL&);
virtual void OnFinalMessage(HWND /*hwnd*/);
...
private:
CDocument<CMainWindow> m_doc;
CView<CMainWindow> m_view;
};
The view handles the messages it wants and
chains the rest to the document:
template <typename TMainWindow>
class CView :
public CWindowImpl<CView>,
// Derive from CDynamicChain to support dynamic chaining
public CDynamicChain {
public:
CView(CDocument<TMainWindow>* pdoc) : m_pdoc(pdoc) {
// Set the document-managed string
m_pdoc->SetString(__T("ATL Doc/View"));
}
BEGIN_MSG_MAP(CView)
// Handle view messages
MESSAGE_HANDLER(WM_PAINT, OnPaint)
ALT_MSG_MAP(1) // Handle messages from the main window
CHAIN_MSG_MAP_DYNAMIC(1) // Route messages to the document
END_MSG_MAP()
private:
LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&);
private:
// View caches its own document pointer
CDocument<TMainWindow>* m_pdoc;
};
The document handles any messages it receives
from the view:
template <typename TMainWindow>
class CDocument :
// Derive from CMessageMap to receive dynamically
// chained messages
public CMessageMap {
public:
BEGIN_MSG_MAP(CDocument)
// Handle messages from the view and the main frame
ALT_MSG_MAP(1)
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
END_MSG_MAP()
CDocument(TMainWindow* pwnd) : m_pwnd(pwnd) { *m_sz = 0; }
void SetString(LPCTSTR psz);
LPCTSTR GetString();
// Message handlers
private:
LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
private:
TMainWindow* m_pwnd;
TCHAR m_sz[64];
};
Filtering Chained
Messages
In many ways, ATL's
CMessageMap is like MFC's CCmdTarget. However,
MFC makes a distinction between command messages and noncommand
messages. Although it's useful for the view and the document to
participate in handling the main window's command messages, the
restfor example, WM_XXXaren't nearly so useful. The view
and the document manage to ignore the rest of the messages using
alternate parts of their message maps, but still, it would be nice
if every message weren't routed this way. Unfortunately, there's no
built-in way to route only command messages using the
message-chaining macros. However, a custom macro could do the
trick:
#define CHAIN_COMMAND_DYNAMIC(dynaChainID) { \
if ((uMsg == WM_COMMAND) && (HIWORD(wParam) == 0) && \
CDynamicChain::CallChain(dynaChainID, hWnd, uMsg, \
wParam, lParam, lResult)) \
return TRUE; \
}
This macro would chain only
WM_COMMAND messages with a code of 0that is, menu
commands, very much like MFC does. However, you'd still need
corresponding equivalents for the nondynamic message-chaining
macros.
|