ATL 3.0 Window Classes - An Introduction
- 格式:pdf
- 大小:251.50 KB
- 文档页数:19
ATL 3.0 Window Classes: An Introduction Michael ParkMicrosoft CorporationJuly 1999Summary: Discusses classes in Active Template Library (ATL) 3.0, which simplify Microsoft® Windows® programming by creating an object-oriented framework around the Windows API, while imposing very little overhead. Examines CWindow, a thin wrapper class; message handling with CWindowImpl and message maps; ATL dialog box classes; and techniques for extending the functionality of existing window classes.ContentsIntroductionCWindowCWindowImplA Simple but Complete ExampleMessage MapsAdding Functionality to Existing Window ClassesBase Class ChainingWindow SuperclassingWindow SubclassingContained WindowsMessage ReflectionATL Dialog Box ClassesSpecifying Window Class InformationConclusionIntroductionAlthough the Active Template Library (ATL) is known primarily for its COM support, it also provides several classes that simplify Microsoft Windows programming. These classes, like the rest of ATL, are template-based and have very low overhead. This article demonstrates the basics of using ATL to create windows and dialog boxes and to handle messages.This article assumes you are familiar with C++ and Windows programming; it does not require that you have any knowledge of COM.CWindowThe most basic ATL window class is CWindow, an object-oriented wrapper for the Windows API. It encapsulates a window handle and provides member functions that wrap the API.Standard Windows programming looks something like this:HWND hWnd = ::CreateWindow( "button", "Click me",WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, NULL, NULL, hInstance, NULL );::ShowWindow( hWnd, nCmdShow );::UpdateWindow( hWnd );The equivalent code using ATL's CWindow class isCWindow win;win.Create( "button", NULL, CWindow::rcDefault, "Click me",WS_CHILD );win.ShowWindow( nCmdShow );win.UpdateWindow();It is useful to keep in mind that an ATL window object is not the same thing as a window. A window, in this context, is a set of data that the Windows operating system uses to manage an area on a screen. An ATL window object, such as an instance of CWindow, is a C++ object, and by itself has no notion of screens or window data structures. Instead, it has one data member, m_hWnd, a window handle. The window handle is the link between a CWindow object and its corresponding window on the screen.One consequence of the division between ATL window objects and windows is that the construction of a CWindow object is distinct from the creation of its window. Referring to the previous example, we see that first the CWindow object is constructed:CWindow win;Then its window is created:win.Create( "button", NULL, CWindow::rcDefault, "Howdy",WS_OVERLAPPEDWINDOW );You can also construct a CWindow object and attach it to an existing window, thereby allowing you to manipulate the existing window with the member functions of CWindow. This can be very useful, since CWindow actually provides more than just convenient wrapper functions; CenterWindow, GetDescendantWindow, and others provide functionality beyond the Windows API.HWND hWnd = CreateWindow( szWndClass, "Main window",WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, NULL, NULL, hInstance, NULL );// choose one of// CWindow win( hWnd ); // attach via constructor// or// CWindow win;// win = hWnd; // attach via operator=// or// CWindow win;// win.Attach( hWnd ); // attach via Attach()win.CenterWindow(); // now can work with win in place of hWndwin.ShowWindow( nCmdShow );win.UpdateWindow();CWindow also has an HWND conversion operator so you can use a CWindow object where an HWND is expected:::ShowWindow( win, nCmdShow ); // API call expects HWNDCWindow makes window manipulation easier, and remarkably, adds no overhead—the optimized compiled code is exactly equivalent to pure Windows API programming using window handles. Unfortunately, CWindow does not let you define how a window responds to messages. Sure, you can use CWindow functions to center a window or hide a window, even send a message to a window, but what happens when the message reaches the window depends on its window class, and that was determined when the window was created. If it was created as an instance of the "button" class, it acts as a button; if it's a "list box" then that's how it behaves. There is no way, using CWindow, to alter that. Fortunately, ATL has another class, CWindowImpl, which does allow you to specify new behavior for a window.CWindowImplCWindowImpl inherits from CWindow, so you can still use all those handy CWindow member functions, but what makes CWindowImpl special is that it also lets you define message-handling behavior. In traditional Windows programming, when you want to specify a window's response to messages, you write a window procedure; in ATL, you define a "message map" in your ATL window class.First, derive your class from CWindowImpl, like this:class CMyWindow : public CWindowImpl<CMyWindow>{Note that your new class's name must be passed as an argument to the CWindowImpl template. Within the class definition, define the message map:BEGIN_MSG_MAP(CMyWindow)MESSAGE_HANDLER(WM_PAINT,OnPaint)MESSAGE_HANDLER(WM_CREATE,OnCreate)MESSAGE_HANDLER(WM_DESTROY,OnDestroy)END_MSG_MAP()The following line:MESSAGE_HANDLER(WM_PAINT,OnPaint)means "When the window receives a WM_PAINT message, invoke member function CMyWindow::OnPaint."Define the member functions that handle the messages:LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ){ ...}LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ){ ...}LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ){ ...}}; // CMyWindowThe four arguments to the handler functions are the message identifier, two parameters whose contents depends on the message, and a flag that the handler can use to indicate that the message has been dealt with or needs further processing. These arguments are examined in more detail in Message Maps.When the window receives a message, the message map entries are examined starting at the top, so it's a good idea to put the most frequent messages first. If no matching entry is found in the message map, the message is passed to the default window procedure.The ATL message map encapsulates the window's message-handling behavior within the class. It is also more readable than a traditional window procedure's nested switch and if statements.To create a window based on a CWindowImpl-derived class, invoke the CWindowImpl Create member function:CMyWindow wnd; // constructs a CMyWindow objectwnd.Create( NULL, CWindow::rcDefault, _T("Hello"),WS_OVERLAPPEDWINDOW|WS_VISIBLE );Note that this Create differs from the Create of CWindow. When creating a window for a CWindow object, you have to specify a registered window class; in contrast, a CWindowImpl-derived class defines a new window class, so no class name need be specified in the call to Create.A Simple but Complete ExampleMost of the examples in this article are just code fragments, but this one is a complete "Hello world" program written with ATL window classes. Although it uses ATL, it does not use COM, so it can be built in Visual C++® as a Win32® application rather than as an ATL COM project.In stdafx.h, put these lines:#include <atlbase.h>extern CComModule _Module;#include <atlwin.h>In hello.cpp, put these:#include "stdafx.h"CComModule _Module;class CMyWindow : public CWindowImpl<CMyWindow> {BEGIN_MSG_MAP( CMyWindow )MESSAGE_HANDLER( WM_PAINT, OnPaint )MESSAGE_HANDLER( WM_DESTROY, OnDestroy )END_MSG_MAP()LRESULT OnPaint( UINT, WPARAM, LPARAM, BOOL& ){PAINTSTRUCT ps;HDC hDC = GetDC();BeginPaint( &ps );TextOut( hDC, 0, 0, _T("Hello world"), 11 );EndPaint( &ps );return 0;}LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ){PostQuitMessage( 0 );return 0;}};int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int ){_Module.Init( NULL, hInstance );CMyWindow wnd;wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),WS_OVERLAPPEDWINDOW|WS_VISIBLE );MSG msg;while( GetMessage( &msg, NULL, 0, 0 ) ){TranslateMessage( &msg );DispatchMessage( &msg );}_Module.Term();return msg.wParam;}CMyWindow is derived from CWindowImpl. Its message map handles two messages,WM_PAINT and WM_DESTROY. When a WM_PAINT message is received, member function OnPaint handles it by drawing "Hello world" in the window. When a WM_DESTROY message is received, signaling that the user has closed the window, OnDestroy calls PostQuitMessage to terminate the application's message loop.WinMain creates an instance of the CMyWindow class and implements a standard message loop. (There is also a small amount of ATL bookkeeping required with respect to _Module.)Message MapsThere are three groups of message handling macros:z Message handlers, for all messages (such as WM_CREATE, WM_PAINT, and so on).z Command handlers, specifically for WM_COMMAND messages (typically sent by predefined child window controls, such as buttons or menu items).z Notification handlers, specifically for WM_NOTIFY messages (typically sent by common controls, such as status bars or list view controls).Message Handler MacrosThere are two message handler macros:z MESSAGE_HANDLERz MESSAGE_RANGE_HANDLERThe first macro maps a specific message to a handler function; the second maps a range of messages to a handler.Message handler functions have the following prototype:LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);where uMsg identifies the message, and wParam and lParam are the message parameters (their contents depend on the message).A handler function uses the bHandled flag to indicate whether it handled the message. If bhandled is set to FALSE in the handler function, the rest of the message map will be searched for another handler for the message. This can be useful when you want to have multiple handlers for the same message, perhaps in different classes using chaining, or if you just want to do something in response to a message but not actually handle it. The bHandled flag is set to TRUE before the function is called, so unless the function explicitly sets bHandled to FALSE, no further message map processing will be performed.Command Handler MacrosCommand handler macros only handle commands (WM_COMMAND messages), but they allow you to specify the mapping in terms of the command code or the identifier of the control sending the command.z COMMAND_HANDLER maps commands with a specified command code from a specifiedcontrol to a handler function.z COMMAND_ID_HANDLER maps commands with any command code from a specified control to a handler function.z COMMAND_CODE_HANDLER maps commands with a specified command code from any control to a handler function.z COMMAND_RANGE_HANDLER maps commands with any command code from a range of controls to a handler function.z COMMAND_RANGE_CODE_HANDLER maps commands with a specified command code froma range controls to a handler function.Command handler functions have the following prototype:LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);where wNotifyCode is the notification code, wID is the identifier of the control sending the command, hWndCtl is the handle of the control sending the command, and bHandled is as described previously.Notification Handler MacrosNotification handler macros map notifications (WM_NOTIFY messages) to functions, depending on the notification code and the identifier of the control sending the notification. These macros are equivalent to the command handler macros described in the preceding section, except that these handle notifications, not commands.z NOTIFY_HANDLERz NOTIFY_ID_HANDLERz NOTIFY_CODE_HANDLERz NOTIFY_RANGE_HANDLERz NOTIFY_RANGE_CODE_HANDLERNotification handler functions have the following prototype:LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);where idCtrl is the identifier of the control sending the notification, pnmh is a pointer to an NMHDR structure, and bHandled is as described previously.Notifications include a pointer to a notification-specific structure; for example, when a list view sends a notification, it includes a pointer to a NMLVDISPINFO structure. All such structures have an NMHDR header, which is what pnmh points to. To access other members of the structure, cast pnmh to a pointer to the specific structure.For example, to handle an LVN_ENDLABELEDIT notification from a list view, put the following into the message map of the parent window:NOTIFY_HANDLER( ID_LISTVIEW, LVN_ENDLABELEDIT, OnEndLabelEdit)This notification sends additional information in an NMLVDISPINFO structure. The notification handler function might look something like this:LRESULT OnEndLabelEdit(int idCtrl, LPNMHDR pnmh, BOOL& bHandled){// The item is -1 if editing is being canceled.if ( ((NMLVDISPINFO*)pnmh)->item.iItem == -1) return FALSE;...Note how pnmh is cast to an NMLVDISPINFO* to allow access to members beyond the NMHDR header.Adding Functionality to Existing Window ClassesThere are several ways to add functionality to an existing window class. If the class is an ATL window class, you can derive a new class from it, as described in Base Class Chaining. This is basically C++ inheritance with just a small twist because of message maps.If you want to extend the capabilities of a predefined window class, such as the button or list box controls, you can superclass it. This defines a new class that is based on the predefined class, but with a message map that adds to the functionality of the underlying window class.Sometimes you'll want to modify the behavior of a window instance, rather than a class—perhaps you need an edit control on a dialog box to do something special. In that case you can write an ATL window class that subclasses the existing edit control. Messages directed to the edit control are routed first through the message map of the subclassing object.Alternatively, you could make the edit control a contained window, a window that passes the messages it receives to the message map of its containing class for processing; the containing class can implement special message handling behavior for the contained window.Finally, there is message reflection, in which the window receiving a message does not handle it, but instead reflects it back to the sending window, which must then handle the message itself. This technique can help make controls more self-contained.Base Class ChainingHaving defined an ATL windowing class with some functionality, you can derive a new class from itto take advantage of inheritance. For example:class CBase: public CWindowImpl< CBase >// simple base window class: shuts down app when closed{BEGIN_MSG_MAP( CBase )MESSAGE_HANDLER( WM_DESTROY, OnDestroy )END_MSG_MAP()LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ){PostQuitMessage( 0 );return 0;}};class CDerived: public CBase// derived from CBase; handles mouse button events{BEGIN_MSG_MAP( CDerived )MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )END_MSG_MAP()LRESULT OnButtonDown( UINT, WPARAM, LPARAM, BOOL& ){ATLTRACE( "button down\n" );return 0;}};// in WinMain():...CDerived win;win.Create( NULL, CWindow::rcDefault, "derived window" );However, there is a problem with this example. If you run this program in the debugger, a windowwill appear. If you click inside the window, "button down" will appear in the Debug output window. This is behavior of CDerived. However, when you close the CDerived window, the application willnot stop executing, even though CBase handles WM_DESTROY messages and CDerived inherits from CBase.Why not? Because you have to explicitly "chain" one message map to another:BEGIN_MSG_MAP( CDerived )MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )CHAIN_MSG_MAP( CBase ) // chain to base classEND_MSG_MAP()Now any messages not handled in the message map of CDerived will be passed to the messagemap of CBase for processing.Why isn't chaining from a class to its base class done automatically? That's because of multipleinheritance, which is fundamental to ATL's architecture. In general, there is no way to know which one of multiple base classes to chain to, so the decision is left to the programmer.Alternate Message MapsMessage map chaining allows one message map to handle messages from multiple window classes, which creates a problem: The same WM_PAINT handler (for instance) is called for all window classes in the chain, even though you presumably want different painting behavior depending on the class. To solve this problem, ATL uses alternate message maps: You divide a message map into sections, each identified by a number. Each such section is an alternate message map:// in class CBase:BEGIN_MSG_MAP( CBase )MESSAGE_HANDLER( WM_CREATE, OnCreate1 )MESSAGE_HANDLER( WM_PAINT, OnPaint1 )ALT_MSG_MAP( 100 )MESSAGE_HANDLER( WM_CREATE, OnCreate2 )MESSAGE_HANDLER( WM_PAINT, OnPaint2 )ALT_MSG_MAP( 101)MESSAGE_HANDLER( WM_CREATE, OnCreate3 )MESSAGE_HANDLER( WM_PAINT, OnPaint3 )END_MSG_MAP()The message map of CBase consists of three sections: the default message map (implicitly numbered 0) and two alternate message maps (numbered 100 and 101).When you chain to the message map, specify the identifier of the desired alternate message map. For example:class CDerived: public CBase {BEGIN_MSG_MAP( CDerived )CHAIN_MSG_MAP_ALT( CBase, 100 )END_MSG_MAP()...The message map of CDerived chains to alternate message map 100 in CBase, so when a CDerived window receives a WM_PAINT message, handler function CBase::OnPaint2 will be invoked.Other Kinds of ChainingIn addition to base class chaining, ATL also supports member chaining and dynamic chaining. These rarely used chaining techniques are outside the scope of this paper, but to cover them briefly, member chaining allows you to chain to the message map of a member variable, and dynamic chaining makes it possible to chain message maps at run time. For more information, seeCHAIN_MSG_MAP_DYNAMIC and CHAIN_MSG_MAP_MEMBER in the online ATL documentation.Window SuperclassingSuperclassing defines a class that adds new functionality to a predefined window class, such as the button or list box controls. The following example superclasses the button control to define a button that beeps when clicked:class CBeepButton: public CWindowImpl< CBeepButton >{public:DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("Button") )BEGIN_MSG_MAP( CBeepButton )MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )END_MSG_MAP()LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled ){MessageBeep( MB_ICONASTERISK );bHandled = FALSE; // alternatively: DefWindowProc()return 0;}}; // CBeepButtonThe DECLARE_WND_SUPERCLASS macro declares the name of the class ("MyButton") and the name of the class being superclassed ("Button"). The message map has one entry, mappingWM_LBUTTONDOWN to OnLButtonDown. All other messages are handled by the default window procedure. Other than beeping, a CBeepButton window should behave as a normal button, so after OnLButtonDown calls MessageBeep, it sets bHandled to FALSE to let the default window procedure handle the message after OnLButtonDown is finished. (Alternatively, it could call DefWindowProc directly.)So far, all we've done is to define a new class; we still have to create some actual CBeepButton windows. The following class has two data members of type CBeepButton; when an instance of this class is created, it creates two CBeepButton child windows.const int ID_BUTTON1 = 101;const int ID_BUTTON2 = 102;class CMyWindow: public CWindowImpl< CMyWindow, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE> > {CBeepButton b1, b2;BEGIN_MSG_MAP( CMyWindow )MESSAGE_HANDLER( WM_CREATE, OnCreate )COMMAND_CODE_HANDLER( BN_CLICKED, OnClick )END_MSG_MAP()LRESULT OnClick(WORD wNotifyCode, WORD wID, HWND hWndCtl,BOOL& bHandled){ATLTRACE( "Control %d clicked\n", wID );return 0;}LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& ){RECT r1 = { 10, 10, 250, 80 };b1.Create(*this, r1, "beep1", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON1);RECT r2 = { 10, 110, 250, 180 };b2.Create(*this, r2, "beep2", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON2);return 0;}}; // CMyWindowWindow SubclassingSubclassing allows you to change the behavior of an existing window, typically a control, by inserting a message map to intercept the window's messages. For example, suppose you have a dialog box with an edit control that you want to accept only non-numeric characters. You could do this by intercepting WM_CHAR messages destined for the edit control and discarding any messages indicating that a numeric character has been entered. Here is a class that does just that: class CNoNumEdit: public CWindowImpl< CNoNumEdit >{BEGIN_MSG_MAP( CNoNumEdit )MESSAGE_HANDLER( WM_CHAR, OnChar )END_MSG_MAP()LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled ){TCHAR ch = wParam;if( _T('0') <= ch && ch <= _T('9') )MessageBeep( 0 );elsebHandled = FALSE;return 0;}};This class handles one message, WM_CHAR. If the character typed is a numeral, it is handled by calling MessageBeep and returning, effectively ignoring the character. For non-numeric characters, bHandled is set to FALSE, indicating that the default window procedure should handle the character.Now we have to subclass an edit control so that CNonNumEdit gets first crack at the control's messages. (The following example uses CDialogImpl, which will be described in the section ATL Dialog Box Classes.) In this example, CMyDialog represents a dialog box resource (identified by IDD_DIALOG1) with an edit control (IDC_EDIT1). When the dialog box is initialized, the edit control is turned into a non-numeric edit control via SubclassWindow:class CMyDialog: public CDialogImpl<CMyDialog>{public:enum { IDD = IDD_DIALOG1 };BEGIN_MSG_MAP( CMyDialog )MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )END_MSG_MAP()LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& ){ed.SubclassWindow( GetDlgItem( IDC_EDIT1 ) );return 0;}CNoNumEdit ed;};Contained WindowsA contained window is a window that doesn't handle any messages; instead, it passes all messages it receives to the message map of another window, its container. Typically the contained window is a child window and its container is its parent window, but that is not always the case. The containing window is not necessarily the same as the parent window. The container–contained window relationship is defined in C++: the contained window is a data member of the container class. The relationship between parent and child windows on the screen is determined when the windows are created.A contained window is based on a registered window class, such as an edit control. If an edit control is contained, the messages that are sent to it are actually handled in the containing window. In this way, the container can make the edit control behave in nonstandard ways. This is similar to subclassing but differs in that no new class need be defined to subclass the control. Compare the previous example, which defined the class CNoNumEdit to handle WM_CHAR messages, with the following, which has the WM_CHAR handler in the containing window class:class CMyWindow: public CWindowImpl<CMyWindow>{CContainedWindow m_contained;public:CMyWindow(): m_contained( _T("edit"), this, 99 ){}...CMyWindow is the container window class. The constructor initializes the CContainedWindow member as follows: The contained window is based on the edit control, and it forwards its messages to "this" (the parent window), using alternate message map 99.BEGIN_MSG_MAP( CMyWindow )MESSAGE_HANDLER( WM_CREATE, OnCreate )MESSAGE_HANDLER( WM_DESTROY, OnDestroy )ALT_MSG_MAP( 99 ) // contained window's messages come here...MESSAGE_HANDLER( WM_CHAR, OnChar )END_MSG_MAP()The parent window, when it is created, creates the contained window (in the WM_CREATE message handler). Since the contained window is based on the edit control, it will look like an edit control on the screen:LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& ){RECT rc = { 10, 10, 200, 35 };m_contained.Create( *this, rc, _T("non-numeric edit"),WS_CHILD|WS_VISIBLE|WS_BORDER, 0, 666 );return 0;}In this example, the container is also the parent window of the contained window.The OnChar member function of the containing window is called when the contained window receives a WM_CHAR message. This is the same function as in the CNoNumEdit example, but in this case, it's a member of the container:LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled ){TCHAR ch = wParam;if( _T('0') <= ch && ch <= _T('9') )MessageBeep( 0 );elsebHandled = FALSE;return 0;}LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ){PostQuitMessage( 0 );return 0;}};You can also use a contained window to subclass an existing child window control, such as a controlon a dialog box; again, unlike regular subclassing, the subclassed window's messages are handled by its containing window. In this next example, a dialog box subclasses an edit control, turning itinto a contained window; the dialog box (container) handles WM_CHAR messages sent to the edit control and causes numeric characters to be ignored. (CDialogImpl is described in ATL Dialog Box Classes.)class CMyDialog: public CDialogImpl<CMyDialog>{public:enum { IDD = IDD_DIALOG1 };// contained window is an edit control:CMyDialog(): m_contained( "edit", this, 123 ){}BEGIN_MSG_MAP( CMyDialog )MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )ALT_MSG_MAP( 123 ) // contained window's messages come here...MESSAGE_HANDLER( WM_CHAR, OnChar )END_MSG_MAP()LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& bHandled ){// when the dialog box is created, subclass its edit control:m_contained.SubclassWindow( GetDlgItem(IDC_EDIT1) );bHandled = FALSE;return 0;}LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled ){TCHAR ch = wParam;if( _T('0') <= ch && ch <= _T('9') )MessageBeep( 0 );elsebHandled = FALSE;return 0;}CContainedWindow m_contained;};Message ReflectionThe previous sections present several techniques for extending the functionality of a window class by changing how a window responds to messages sent to it. In contrast, message reflection involves getting a window to respond to messages that it sends out.When the user interacts with a control, the control typically informs its parent window by sending ita WM_COMMAND or WM_NOTIFY message; the parent window then takes some action in response. For example:class CParentWindow: CWindowImpl<CParentWindow>{// assume that this window will have a child// button control with an ID of ID_BUTTONBEGIN_MSG_MAP( CParentWindow )COMMAND_ID_HANDLER( ID_BUTTON, OnButton )MESSAGE_HANDLER( WM_CTLCOLORBUTTON, OnColorButton )...When the button is clicked, it sends a command to the parent window, andCParentWindow::OnButton is invoked. Similarly, when the button is about to be drawn, it sends a WM_CTLCOLORBUTTON message to the parent; CParentWindow::OnColorButton responds。