|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
README.TXTThis is the stuff I want you to read first, before proceeding on or posting messages to this article's discussion board. This series of articles originally covered WTL 7.0 and written for VC 6 users. Now that VC 8 is out, I felt it was about time to update the articles to cover VC 7.1. ;) (Also, the automatic 6-to-7 conversion done by VC 7.1 doesn't always go smoothly so VC 7.1 users could get stuck when trying to use the demo source code.) So as I go through and update this series, the articles will be updated to reflect new WTL 7.1 features, and I'll have VC 7.1 projects in the source downloads. Important note for VC 2005 users: The Express edition of VC 2005 does not come with ATL (or MFC for that matter) so you can't build ATL or WTL projects with the Express version. If you are using VC 6, then you need the Platform SDK. You can't use WTL without it. You can use the web install version, or download the CAB files or an ISO image and run the setup locally. Be sure you use the utility to add the SDK include and lib directories to the VC search path. You can find this in the Visual Studio Registration folder in the Platform SDK program group. It's a good idea to get the latest Platform SDK even if you're using VC 7 so you have the latest headers and libs. You need WTL. Download version 7.1 from Microsoft. See the articles "Introduction to WTL - Part 1" and "Easy installation of WTL" for some tips on installing the files. Those articles are rather out-of-date now, but still contain some good info. The WTL distribution also has a readme file with installation instructions. One thing which I don't think is mentioned in those articles is how to add the WTL files to the VC include path. In VC 6, click Tools|Options and go to the Directories tab. In the Show directories for combo box, select Include files. Then add a new entry that points to the directory where you put the WTL header files. In VC 7, click Tools|Options, click Projects then VC++ Directories. In the Show directories for combo box, select Include files. Then add a new entry that points to the directory where you put the WTL header files. Important: While we're on the subject of the VC 7 include path, you must make a change to the default directory list if you haven't updated your Platform SDK. Make sure that
You need to know MFC, and know it well enough that you understand what's behind the message map macros, and can edit the code marked "DO NOT EDIT" with no problems. You need to know Win32 API programming, and know it well. If you learned Windows programming by going straight into MFC, without learning how messages work at the API level, you are unfortunately going to have trouble in WTL. If you don't know what a message's You need to know C++ template syntax. See the VC Forum FAQ for links to C++ FAQs and template FAQs. Since I haven't used VC 8 yet, I don't know if the sample code will compile on 8. Hopefully the 7-to-8 upgrade process will work better than the 6-to-7 process did. Please post on this article's forum if you have any trouble with VC 8. Introduction to the SeriesWTL rocks. It does. It has a lot of the power of MFC's GUI classes, yet produces substantially smaller executables. If you're like me, and learned GUI programming with MFC, you've become quite comfortable with all the control wrappers MFC provides, as well as with the flexible message handling built into MFC. If you're also like me and don't relish the idea of several hundred K of MFC framework getting added to your programs, WTL is for you. However, there are a few barriers we have to overcome:
On the other hand, the benefits of WTL are:
In this series, I will first introduce the ATL windowing classes. WTL is, after all, a set of add-on classes to ATL, so a good understanding of ATL windowing is essential. Once I've covered that, I'll introduce WTL features and show how they make GUI programming a lot easier. Introduction to Part IWTL rocks. But before we get to why, we need to cover ATL first. WTL is a set of add-on classes to ATL, and if you've been strictly an MFC programmer in the past, you may never have encountered the ATL GUI classes. So please bear with me if I don't get to WTL right off the bat; the detour into ATL is necessary. In this first part, I will give a little background on ATL, cover some essential points you need to know before writing ATL code, quickly explain those funky ATL templates, and cover the basic ATL windowing classes. ATL BackgroundATL and WTL historyThe Active Template Library... kind of an odd name, isn't it? Old-timers might remember that it was originally the ActiveX Template Library, which is a more accurate name, since ATL's goal is to make writing COM objects and ActiveX controls easier. (ATL was also developed during the time that Microsoft was naming new products ActiveX-something, just as new products nowadays are called something.NET.) Since ATL is really about writing COM objects, it only has the most basic of GUI classes, the equivalent of MFC's WTL had two major revisions as a Microsoft-owned project, numbered 3 and 7. (The version numbers were chosen to match the ATL version numbers, that's why they're not 1 and 2.) Version 3.1 is obsolete now so it will not be covered in this series. Version 7.0 was a major update to version 3, and version 7.1 added some bug fixes and minor features. Following version 7.1, Microsoft made WTL an open-source project, hosted at Sourceforge. The latest release from that site is version 7.5. I have not looked at 7.5 yet, so this series will not cover 7.5 at this time. (I'm already behind by two versions! I'll catch up eventually.) ATL-style templatesEven if you can read C++ templates without getting a headache, there are two things ATL does that might trip you up at first. Take this class for example: class CMyWnd : public CWindowImpl<CMyWnd> { ... }; That actually is legal, because the C++ spec says that immediately after the To see this in action, look at this set of classes: template <class T> class B1 { public: void SayHi() { T* pT = static_cast<T*>(this); // HUH?? I'll explain this below pT->PrintClassName(); } void PrintClassName() { cout << "This is B1"; } }; class D1 : public B1<D1> { // No overridden functions at all }; class D2 : public B1<D2> { void PrintClassName() { cout << "This is D2"; } }; int main() { D1 d1; D2 d2; d1.SayHi(); // prints "This is B1" d2.SayHi(); // prints "This is D2" return 0; } The To explain how this works, let's look at each call to void B1<D1>::SayHi() { D1* pT = static_cast<D1*>(this); pT->PrintClassName(); } Since Now, take the second call to void B1<D2>::SayHi() { D2* pT = static_cast<D2*>(this); pT->PrintClassName(); } This time, The benefits of this technique are:
While the saving of a vtbl doesn't seem significant in this example (it would only be 4 bytes), think of the case where there are 15 base classes, some of those containing 20 methods, and the savings adds up. ATL Windowing ClassesOK, enough background! Time to dive into ATL. ATL is designed with a strict interface/implementation division, and that's evident in the windowing classes. This is similar to COM, where interface definitions are completely separate from an implementation (or possibly several implementations). ATL has one class that defines the "interface" for a window, that is, what can be done with a window. This class is called
The ATL class that has the implementation of a window is There are also two separate classes that contain the implementation of a dialog box, Defining a Window ImplementationAny non-dialog window you create will derive from
The window class definition is done using the Let's start out a new class definition, and I'll keep adding to it as we go through this section. class CMyWindow : public CWindowImpl<CMyWindow> { public: DECLARE_WND_CLASS(_T("My Window Class")) }; Next comes the message map. ATL message maps are much simpler than MFC maps. An ATL map expands into a big switch statement; the switch looks for the right handler and calls the corresponding function. The macros for the message map are class CMyWindow : public CWindowImpl<CMyWindow> { public: DECLARE_WND_CLASS(_T("My Window Class")) BEGIN_MSG_MAP(CMyWindow) END_MSG_MAP() }; I'll cover how to add handlers to the map in the next section. Finally, we need to define the window traits for our class. Window traits are a combination of window styles and extended window styles that are used when creating the window. The styles are specified as template parameters so the caller doesn't have to be bothered with getting the styles right when it creates our window. Here's a sample traits definition using the ATL class typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, WS_EX_APPWINDOW> CMyWindowTraits; class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits> { public: DECLARE_WND_CLASS(_T("My Window Class")) BEGIN_MSG_MAP(CMyWindow) END_MSG_MAP() }; The caller can override the styles in the typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CFrameWinTraits; Filling in the message mapThe ATL message map is one area that is lacking in developer-friendliness, and something that WTL greatly improves on. ClassView does at least give you the ability to add message handers, however ATL doesn't have message-specific macros and automatic parameter unpacking like MFC does. In ATL, there are just three types of message handlers, one for class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits> { public: DECLARE_WND_CLASS(_T("My Window Class")) BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_CLOSE, OnClose) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { DestroyWindow(); return 0; } LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { PostQuitMessage(0); return 0; } }; You'll notice that the handlers get the raw Let's add a class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits> { public: DECLARE_WND_CLASS(_T("My Window Class")) BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_CLOSE, OnClose) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout) END_MSG_MAP() LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { DestroyWindow(); return 0; } LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { PostQuitMessage(0); return 0; } LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { MessageBox ( _T("Sample ATL window"), _T("About MyWindow") ); return 0; } }; Notice that the Advanced Message Maps and Mix-in ClassesOne major difference in ATL is that any C++ class can handle messages, unlike MFC where message-handling duties are split between A base class with a message map is usually a template that takes the derived class name as a template parameter, so it can access members of the derived class like template <class T, COLORREF t_crBrushColor> class CPaintBkgnd { public: CPaintBkgnd() { m_hbrBkgnd = CreateSolidBrush(t_crBrushColor); } ~CPaintBkgnd() { DeleteObject ( m_hbrBkgnd ); } BEGIN_MSG_MAP(CPaintBkgnd) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) END_MSG_MAP() LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { T* pT = static_cast<T*>(this); HDC dc = (HDC) wParam; RECT rcClient; pT->GetClientRect ( &rcClient ); FillRect ( dc, &rcClient, m_hbrBkgnd ); return 1; // we painted the background } protected: HBRUSH m_hbrBkgnd; }; Let's go through this new class. First, The constructor and destructor are pretty simple, they create and destroy a brush of the color passed as To use this mix-in class with our window, we do two things. First, we add it to the inheritance list: class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>, public CPaintBkgnd<CMyWindow, RGB(0,0,255)> Second, we need to get class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>, public CPaintBkgnd<CMyWindow, RGB(0,0,255)> { ... typedef CPaintBkgnd<CMyWindow, RGB(0,0,255)> CPaintBkgndBase; BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_CLOSE, OnClose) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) COMMAND_HANDLER(IDC_ABOUT, OnAbout) CHAIN_MSG_MAP(CPaintBkgndBase) END_MSG_MAP() ... }; Any messages that reach the You could conceivably have several mix-in classes in the inheritance list, each with a Structure of an ATL EXENow that we have a complete (if not entirely useful) main window, let's see how to use it in a program. ATL EXEs have one or more global variables that roughly correspond to the global In VC 6An ATL executable contains a global // stdafx.h: #define STRICT #define WIN32_LEAN_AND_MEAN #include <atlbase.h> // Base ATL classes extern CComModule _Module; // Global _Module #include <atlwin.h> // ATL windowing classes atlbase.h will include the basic Windows headers, so there's no need to include windows.h, tchar.h, etc. In our CPP file, we declare the // main.cpp: CComModule _Module;
// main.cpp: CComModule _Module; int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR szCmdLine, int nCmdShow) { _Module.Init(NULL, hInst); _Module.Term(); } The first parameter to // main.cpp: #include "MyWindow.h" CComModule _Module; int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR szCmdLine, int nCmdShow) { _Module.Init(NULL, hInst); CMyWindow wndMain; MSG msg; // Create & show our main window if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault, _T("My First ATL Window") )) { // Bad news, window creation failed return 1; } wndMain.ShowWindow(nCmdShow); wndMain.UpdateWindow(); // Run the message loop while ( GetMessage(&msg, NULL, 0, 0) > 0 ) { TranslateMessage(&msg); DispatchMessage(&msg); } _Module.Term(); return msg.wParam; } The only unusual thing in the above code is Under the hood, ATL uses some assembly-language black magic to connect the main window's handle to its corresponding In VC 7ATL 7 split up the module-management code into several classes. In VC 7, the ATL headers automatically declare global instances of all the module classes, and the // stdafx.h: #define STRICT #define WIN32_LEAN_AND_MEAN #include <atlbase.h> // Base ATL classes #include <atlwin.h> // ATL windowing classes The // main.cpp: #include "MyWindow.h" int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR szCmdLine, int nCmdShow) { CMyWindow wndMain; MSG msg; // Create & show our main window if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault, _T("My First ATL Window") )) { // Bad news, window creation failed return 1; } wndMain.ShowWindow(nCmdShow); wndMain.UpdateWindow(); // Run the message loop while ( GetMessage(&msg, NULL, 0, 0) > 0 ) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } Here's what our window looks like:
Nothing particularly exciting, I'll admit. To spice it up, we'll add an About menu item that shows a dialog. Dialogs in ATLAs mentioned earlier, ATL has two dialog classes. We'll use
Here is the start of a new class definition for an about dialog: class CAboutDlg : public CDialogImpl<CAboutDlg> { public: enum { IDD = IDD_ABOUT }; BEGIN_MSG_MAP(CAboutDlg) END_MSG_MAP() }; ATL has no built-in handlers for the OK and Cancel buttons, so we need to code them ourselves, along with a class CAboutDlg : public CDialogImpl<CAboutDlg> { public: enum { IDD = IDD_ABOUT }; BEGIN_MSG_MAP(CAboutDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_CLOSE, OnClose) COMMAND_ID_HANDLER(IDOK, OnOKCancel) COMMAND_ID_HANDLER(IDCANCEL, OnOKCancel) END_MSG_MAP() LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { CenterWindow(); return TRUE; // let the system set the focus } LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { EndDialog(IDCANCEL); return 0; } LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { EndDialog(wID); return 0; } }; I used one handler for both OK and Cancel to demonstrate the Showing the dialog is similar to MFC, you create an object of the new class and call class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>, public CPaintBkgnd<CMyWindow,RGB(0,0,255)> { public: BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_CREATE, OnCreate) COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout) // ... CHAIN_MSG_MAP(CPaintBkgndBase) END_MSG_MAP() LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { HMENU hmenu = LoadMenu ( _Module.GetResourceInstance(), // _AtlBaseModule in VC7 MAKEINTRESOURCE(IDR_MENU1) ); SetMenu ( hmenu ); return 0; } LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { CAboutDlg dlg; dlg.DoModal(); return 0; } // ... }; One small difference in modal dialogs is where you specify the dialog's parent window. In MFC you pass the parent to the The Note that Here is our revised main window and the about dialog:
I'll Get to WTL, I Promise!But it will be in Part 2. Since I'm writing these articles for MFC developers, I felt that it was best to do an intro to ATL first, before diving into WTL. If this has been your first exposure to ATL, now might be a good time to write some simple apps on your own, so you get the hang of message maps and using mix-in classes. You can also experiment with ClassView's support for ATL message maps, as it can add message handlers for you. To get started in VC 6, right-click the CMyWindow item and pick Add Windows Message Handler on the context menu. In VC 7, right-click the CMyWindow item and pick Properties on the context menu. In the properties window, click the Messages button on the toolbar to see a list of window messages. You can add a handler for a message by going to its row, clicking the right column to turn it into a combo box, clicking the combo box arrow, then clicking the In Part II, I will cover the base WTL windowing classes, the WTL AppWizard, and the much nicer message map macros. Copyright and licenseThis article is copyrighted material, (c)2003-2005 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here. The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefiting from my code) but is not required. Attribution in your own source code is also appreciated but not required. Revision History
Series Navigation: » Part II (WTL GUI Base Classes)
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||