本文适用于对MFC比较了解的中级开发人员。
源代码下载:http://download.csdn.net/source/3522785
class CMyWnd : public CWindowImpl<CMyWnd> { ... };
看下面例子:
template <class T> class B1 { public: void SayHi() { // 此处是关键技巧。 T* pT = static_cast<T*>(this); 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; }
static_cast<T*
>(
this)
是关键
技术。它将
B1*
的
this
根据不同的调用转化为
D1*
或
D2*
。由于模板代码是在编译时生成的,只要继承列表编写正确,这种类型转换就是安全的。
(
避免写出
class D3 :
public B1
<D2
>这种形式的代码。编译器是检测不出的。)
第一次调用
SayHi()
时,代码实际是这样的:
void B1<D1>::SayHi() { D1* pT = static_cast<D1*>(this); pT->PrintClassName(); }
void B1<D2>::SayHi() { D2* pT = static_cast<D2*>(this); pT->PrintClassName(); }
这种技术的优点:
1) 不需要使用对象的指针
2) 节省内存,因为不需要使用虚函数表
3) 不会因为未初始化的虚函数表导致使用NULL指针
4) 所有函数的调用在编译时确定,因此它们是可以优化的。
ATL严格遵守接口-实现分离的原则。
ATL拥有一个定义Window的接口类:CWindow。它仅仅封装了HWND,并且封装了几乎所有的Use32 APIs中以HWND为第一个参数的接口。例如SetWindowText()
和DestroyWindow()
。
CWindow
提供一个共有的成员
m_hWnd
,可以直接处理
HWND
,也提供了一个
operator HWND()
,可以直接是
CWindow
作为需要
HWND
对象的函数参数。
CWindow
不同于
MFC
中的
CWnd
。
CWindow
易于创建的,它不提供像
MFC
中的
HWND
到
CWnd
的对象关系。当
CWindow
对象超出作用域时,它被销毁,但是它关联的实际窗口不会被销毁。因此不需要
detach
你创建的临时的
CWindow
对象。
ATL
还提供了一个
Window
的实现类
CWindowImpl
。它包含了下列处理:窗口注册、窗口子类、消息映射、以及一个基本的
WindowProc().
不想
MFC
中的
CWnd
,所有的东西都在这个类中。
关于对话框的实现,
ATL
提供了两个独立的实现类
CDialogImpl 和 CAxDialogImpl。前者用于普通对话框 ,后者用于ActiveX控件。
定义一个非对话框的窗口类,要从CWindowImpl派生。新类中必须包含三件事情:
1) 窗口类定义
2) 消息映射
3) 一个默认的窗体特征,叫做window traits
1、
窗口类定义用宏DECLARE_WND_CLASS
或 DECLARE_WND_CLASS_EX.
二者均定义了一个
ATL
结构
CWndClassInfo
,它封装了
WNDCLASSEX
结构。前者仅仅定义新的窗口类的名字,其他参数用默认值;后者还可以定义窗口风格和背景色。窗口类的名字可以是
NULL
,此时
ATL
会自动生成一个。
class CMyWindow : public CWindowImpl<CMyWindow> { public: DECLARE_WND_CLASS(_T("My Window Class")) };
class CMyWindow : public CWindowImpl<CMyWindow> { public: DECLARE_WND_CLASS(_T("My Window Class")) BEGIN_MSG_MAP(CMyWindow) END_MSG_MAP() };
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() };
调用者可以重写CMyWindowTraits的定义,但是这通常是不需要的。ATL也提供了一些预定义的窗体特征类:如CFrameWinTraits
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CFrameWinTraits;
ATL不提供像MFC那样的自动生成消息映射的宏。但是仅仅有三种类型的消息处理:一种像WM_NOTIFY
,
一种像
WM_COMMAND
,另一种就是其他类型的消息处理。
第三类消息
:
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; } };
消息处理函数封装了WPARAM 和 LPARAM,在使用时需要对它们进行解码。对于第四个参数,ATL在消息处理之前会把它设为TRUE,如果你想让ATL的默认WindowProc()
在
本消息处理函数结束之后也处理此消息,需要设置此参数为
FALSE
。这和
MFC
不同,你必须明确调用基类的消息处理函数。
第二类消息:WM_COMMAND
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; } };
同样第一类消息,通过NOTIFY_HANDLER
把
消息参数解码。
ATL中的任一C++类均可处理消息映射。这可以让我们编写混合类,我们通过添加此类到继承列表中来为窗体添加新特性。
一个带消息映射的基类通常是一个把派生类名字作为模板参数的模板类。因此它可以处理派生类的成员,如m_hWnd(the HWND
member inCWindow
)。
下面一个例子是处理WM_ERASEBKGND
的一个混合类。
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; };
首先
CPaintBkgnd类有两个模板参数,一个是将要用此类的派生类名字,另一个是背景色。(t_前缀通常用于值类型模板参数)。构造函数和析构函数用于创建和销毁画刷。消息映射处理WM_ERASEBKGND。最后,OnEraseBkgnd()实现用构造函数中创建的画刷填充窗口。1)它调用了其派生窗口类中的函数GetClientRect(),
如果派生类没有此函数,编译器将会提示错误。2)OnEraseBkgnd()需要从wParam解码出设备上下文。
要使用此混合类,首先把它添加到继承列表,然后用CHAIN_MSG_MAP
宏
传递消息到此类中。
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() ... };
一个窗口类可以有多个混合类,每一个有一个CHAIN_MSG_MAP宏。
以VC8为例:VC7以来,ATL头文件自动声明全局的CComModule实例,以及初始化Init()和终止函数Term()。因此这些步骤就不需要了。
// stdafx.h: #define STRICT #define WIN32_LEAN_AND_MEAN #include <atlbase.h> // Base ATL classes #include <atlwin.h> // ATL windowing classes
函数不需要调用任何
_Module
函数
。
// 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; }
前面讲过ATL有两种Dialog。此处用CDialogImpl。创建一个新的对话框和创建一个新的框架窗口很相似。有两点不同:
1) 用CDialogImpl替换CWindowImpl
2) 定义IDD指向对话框资源的ID
class CAboutDlg : public CDialogImpl<CAboutDlg> { public: enum { IDD = IDD_ABOUT }; BEGIN_MSG_MAP(CAboutDlg) END_MSG_MAP() };
ATL没有定义OK和CANCEL两个button的处理,需要自己定义。处理WM_INITDIALOG消息,初始化对话框的表现。
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; } };
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; } // ... };