Developers Workshop to COM and ATL 3.0
| < Free Open Study > |
|
ATL does provide a handful of templates and utility classes which can ease your Win32 EXE development burden. Many of these templates are not intended for direct use in your application, but exist simply to provide base class functionality to other templates. The core ATL windowing templates are shown below in Figure 13-2, all of which are defined in <atlwin.h>.
Note | If you do not explicitly include <atlwin.h> into your ATL projects, you will not bring in the definitions of the templates seen in Figure 13-2. |
Typically, you only need to be concerned with a small set of the ATL windowing classes, specifically CWindow, CWinTraits<>, CDialogImpl<>, CSimpleDialog<> and CWindowImpl<>. Using this subset, you are given just enough support to build both stand-alone EXEs and interactive dialog boxes.
As you can see, many of the ATL windowing templates have an "-Ax-" infix (for example, CAxHostWindow). Beyond providing the guts of a basic window, these "-Ax-" classes provide additional support for responding to ActiveX control events as well as code to host and site the control within the client area of the window.
ATL also provides the CMessageMap class. This class provides a helper function, which allows for a table-driven WndProc for use by the main window. As you may suspect, ATL has another map (the MSG_MAP) that is used in conjunction with the functionality provided by the CMessageMap base class. Now, before you begin to think "code bloat," rest assured that the ATL MSG_MAP table is extremely lightweight in comparison to MFC's MESSAGE_MAP structure.
The various "-T<>" helper templates (e.g., CWindowImplBaseT<>) provide core functionality to many of the core ATL templates, and for the most part can be safely ignored. Finally, CWinTraits<> allows the ATL developer to store all window styles in a convenient C++ template. To begin learning our way around this new hierarchy, we begin at the top, with CWindow.
Understanding CWindow
CWindow defines an underlying HWND data type and numerous wrappers to Win32 API windowing functions. Recall from our previous Win32 example that each window is identified by a corresponding HWND, which is obtained by a call to CreateWindow(). Also recall that just about every Win32 API windowing function requires an HWND as the first parameter (which should make some sense, as these functions need to know what window they are manipulating!). CWindow maintains a public HWND data member: m_hWnd. This member variable begins life by being set to NULL in the constructor of CWindow (as seen in <atlwin.h>):
// CWindow wraps your HWND. class CWindow { public: static RECT rcDefault; // A default rectangle for the window's size. HWND m_hWnd; CWindow(HWND hWnd = NULL) { m_hWnd = hWnd; } // Tons of methods which wrap API calls and make use of m_hWnd. ... };
The methods of CWindow are far too many to enumerate here and would prove to be redundant, as they are all listed within online help. The code behind these methods do not require much commentary, as they simply call the Win32 API function they are responsible for wrapping, using m_hWnd as the initial parameter.
To get a flavor of some of these member functions, here are a few well-known API calls wrapped by CWindow:
// A sampling of CWindow methods (all defined inline). BOOL Invalidate(BOOL bErase = TRUE) { ATLASSERT(::IsWindow(m_hWnd)); return ::InvalidateRect(m_hWnd, NULL, bErase); } BOOL InvalidateRect(LPCRECT lpRect, BOOL bErase = TRUE) { ATLASSERT(::IsWindow(m_hWnd)); return ::InvalidateRect(m_hWnd, lpRect, bErase); } HDC BeginPaint(LPPAINTSTRUCT lpPaint) { ATLASSERT(::IsWindow(m_hWnd)); return ::BeginPaint(m_hWnd, lpPaint); } void EndPaint(LPPAINTSTRUCT lpPaint) { ATLASSERT(::IsWindow(m_hWnd)); ::EndPaint(m_hWnd, lpPaint); }
I think you get the general idea. Now, as most of the remaining ATL windowing classes take CWindow as a template parameter, you are able to make use of CWindow's functionality as you build your windows and dialog boxes with ATL. In addition to wrapping dozens of global Win32 API calls, CWindow does provide an interesting HWND operator which can prove to be quite helpful:
// CWindow provides an HWND operator. operator HWND() const { return m_hWnd; }
While this looks rather bland, the net result is that you can use a valid CWindow object in place of any Win32 function that requires an HWND parameter. The final point of interest is the Create() method. This method takes a number of parameters (most of which are optional) to indirectly call CreateWindowEx() on your behalf. If this call is successful, the underlying m_hWnd is then set to the newly created window:
// CWindow::Create() wraps the creation of your window. HWND Create(LPCTSTR lpstrWndClass, HWND hWndParent, LPRECT lpRect = NULL, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, HMENU hMenu = NULL, LPVOID lpCreateParam = NULL) { if(lpRect == NULL) lpRect = &rcDefault; m_hWnd = ::CreateWindowEx(dwExStyle, lpstrWndClass, szWindowName, dwStyle, lpRect->left, lpRect->top, lpRect->right - lpRect->left, lpRect->bottom - lpRect->top, hWndParent, hMenu, _Module.GetModuleInstance(), lpCreateParam); return m_hWnd; }
Note | In addition to wrapping the Win32 API, CWindow also contains a set of helper methods for common windowing tasks (e.g., CenterWindow()). See online help for complete details. |
Typically you will not need to directly create a CWindow object. In reality, this class is not extremely useful on its own, but is very useful as a parameter to the remaining ATL windowing templates.
Building a Better Window with ATL
To illustrate the usefulness of ATL's windowing library, let's re-create the previous raw Win32 window example in ATL. After we get this window up and running, we will take a deeper look inside the provided functionality. Keep in mind that whenever you are using any of the ATL windowing templates you must explicitly make a preprocessor include to <atlwin.h>. The best place to do so is within your precompiled header file:
// <stdafx.h> must include <atlwin.h> extern CExeModule _Module; #include <atlwin.h> #include <atlcom.h>
Creating a main window in ATL begins by deriving a custom C++ class from CWindowImpl<>, which takes three template parameters. The first parameter is the name of the class itself, which we will call CMainWindow. CWindow is the default second parameter, which gives CWindowImpl<> most of its functionality. The final default parameter is a CWinTraits<> template, which we will get to in just a moment. Bottom line, here is how we can begin to create a new C++ class which leverages the ATL framework:
// To create an ATL window, begin by deriving from CWindowImpl<>. class CMainWindow : public CWindowImpl<CMainWindow, CWindow, CWinTraits<WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE, 0 > > { public: CMainWindow(); virtual ~CMainWindow(); };
Using CWinTraits<>
CWindowImpl<> takes a CWinTraits<> template as its final parameter. This handy ATL utility class wraps up all the window styles (e.g., WS_xxx) and extended window styles (e.g., WS_EX_xxx) in a nice clean package. CWinTraits<> takes two parameters: an OR-ing together of basic styles and an OR-ing together of extended styles. Because many window types (such as CMainWindow) have a similar set of styles, ATL does provide a set of typedefs to help streamline this final parameter passed into CWindowImpl<>:
// ATL predefines four CWinTraits<> typedefs. 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; typedef CWinTraits<0, 0> CNullTraits;
Note | A related class, CWinTraitsOR<>, allows you to leverage these existing ATL typedefs by OR-ing in additional custom traits. Again, see online help for more details. |
If we make use of CFrameWinTraits, our definition of CMainWindow cleans up quite a bit:
// Slim things down with a CWinTraits<> typedef. class CMainWindow : public CWindowImpl<CMainWindow, CWindow, CFrameWinTraits> { public: ... };
The final point of interest concerning CWinTraits<> is that this template defines two public member functions which allow you to extract a DWORD representing the current set of basic styles (GetWndStyle()) as well as the set of extended styles (GetWndExStyle()):
// You may fetch out the set of styles using CWinTraits<> helper functions. 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; } };
Creating and Destroying the ATL Window
As for the implementation of the constructor of CMainWindow, we will keep things well encapsulated by making a call to CWindowImpl<>::Create(), specifying the size of this window (making use of the default static rectangle defined by CWindow) as well as a caption for the title bar:
// Create and destroy this window. CMainWindow::CMainWindow() { // Create my window. Create(NULL, CWindow::rcDefault, "My ATL Window"); ShowWindow(SW_SHOWNORMAL); }
The destructor can make a quick check to ensure the m_hWnd variable is valid (just to be safe) and call the DestroyWindow() method to clean things up:
// Destroy the underlying m_hWnd when our object is going down. CMainWindow::~CMainWindow() { if(m_hWnd) DestroyWindow(); }
Responding to Windows Messages in ATL
CWindowImpl<> has sheltered us from having to create and register this window by hand, but we are currently unable to receive any messages such as WM_PAINT, WM_DESTROY, or WM_CREATE. As CWindowImpl<> inherits some of its functionality from CMessageMap, we must ensure that our derived class has a valid "message map" defined within it. The ATL MSG_MAP provides a lightweight way to route a given Windows' message to a specific member function in your class. Unlike a traditional Win32 API program, we will not have to construct a WndProc function by hand and test the incoming message with a gigantic switch statement. Rather, we will populate our MSG_MAP with a number of macros to do the grunt work of responding for a given message. You can define an empty MSG_MAP as so:
// In order to create our CMainWindow, we must supply a message map. class CMainWindow : public CWindowImpl<CMainWindow, CWindow, CWinTraits<WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE, 0 > > { public: CMainWindow(); virtual ~CMainWindow(); // Enable a table driven WndProc. BEGIN_MSG_MAP(CMainWindow) END_MSG_MAP() };
We will take a closer look at these macros and CMessageMap later. For the time being, just understand that this map provides the ATL equivalent of the raw WndProc we wrote by hand in the previous Win32 example.
The Add Windows Message Handler Wizard
Once you have defined an empty MSG_MAP in an ATL-based window you are now ready to populate the map using the Add Windows Message Handler CASE tool. To activate this tool, right-click on your MSG_MAP savvy class from ClassView. You will be presented with the following dialog box:
Like all CASE tools, this new ATL wizard simply saves you some typing. You are always free to edit your MSG_MAP by hand, and in some cases you may need to. In many ways, this tool can be seen as a no-frills version of MFC's ClassWizard utility (in fact, you may have bumped into this same tool when using the Visual C++ dialog editor).
The process of routing a message to an ATL window class is simple: Select the class to handle from the appropriate list box (in this case CMainWindow), then select the message(s) you wish to respond to by double-clicking on the message name (or select one and click Add Handler). For this example, go ahead and select WM_PAINT and close the tool.
If you examine the header file of your CMainFrame class, you will see your MSG_MAP has been updated with a new MESSAGE_HANDLER macro, as well as stub code for an inlined function named OnPaint. Following the lead of MFC, all standard windows message handlers are named by dropping the "WM_" prefix in place of "On-" followed by the name of the message in lowercase (for example: WM_PAINT == OnPaint, WM_CREATE == OnCreate, WM_DESTROY == OnDestroy, and so forth):
// The Add Windows Message Handler Wizard populates your MSG_MAP. class CMainWindow : public CWindowImpl<CMainWindow, CWindow, CFrameWinTraits> { public: CMainWindow(); virtual ~CMainWindow(); // The WM_PAINT message will now be routed to OnPaint(). BEGIN_MSG_MAP(CMainWindow) MESSAGE_HANDLER(WM_PAINT, OnPaint) END_MSG_MAP() LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { return 0; } };
Note | Like all ATL CASE tools, there is no corresponding option to remove code entered with this tool. To do so by hand, delete the MESSAGE_HAN- DLER macro from your message map as well as the generated stub code. |
We will see exactly what MESSAGE_HANDLER resolves to in a bit; however, its purpose should be clear. When a given message is routed to your window (e.g., WM_PAINT) the framework will call a given function (e.g., OnPaint()). The signature used for each function handled using the MESSAGE_HANDLER macro will always have the same signature as seen here. All that is left to do is write the code to execute in response to this message:
// Paint some centered text into the client area. LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // This example makes use of a number of Win32 API calls. // See online help (if necessary). PAINTSTRUCT ps; BeginPaint(&ps); HDC hDC = GetDC(); RECT r; GetClientRect(&r); DrawText(hDC, "My ATL Window", -1, &r, DT_SINGLELINE | DT_VCENTER | DT_CENTER); EndPaint(&ps); ReleaseDC(hDC); return 0; }
If we were now to create an instance of our CMainWindow class, we would see a full-fledged window come to life (Figure 13-4). The following code snippet will do nicely:
// Create the ATL window. CMainWindow theWind;
On the CD The companion CD contains the SimpleATLWindow project which illustrates the development and use of CMainWindow.
Our simple ATL window handles only a single message, which is not making use of any of the parameters sent into the message handler function (WPARAM, LPARAM, and so on). If you are a Win32 developer, you are aware that each Windows message uses the information contained in the WPARAM and LPARAM parameters in its own unique way. For example, if you are handling a WM_MOUSEMOVE message, you can "crack" out information contained in these parameters to discover relevant mouse-related details (the [x, y] coordinates of the cursor, the state of the keyboard, and so on) from the LPARAM parameter. ATL provides little help with the cracking of message-specific information, and you will need to fall back on your understanding of the Win32 API to fully work within the realm of ATL windows development. This topic is beyond the scope of this book, so find that copy of Petzold!
| < Free Open Study > |
|