高级消息映射链和嵌入类
ATL的另一个显著不同之处就是任何一个C++(www.cppentry.com)类都可以响应消息,而MFC只是将消息响应任务分给了CWnd类和CCmdTarget类,外加几个有PreTranslateMessage()方法的类。ATL的这种特性允许我们编写所谓的“嵌入类”,为我们的窗口添加特性只需将该类添加到继承列表中就行了,就这么简单!
一个基本的带有消息映射链的类通常是模板类,将派生类的类名作为模板的参数,这样它就可以访问派生类中的成员,比如m_hWnd(CWindow类中的HWND成员)。让我们来看一个嵌入类的例子,这个嵌入类通过响应WM_ERASEBKGND消息来画窗口的背景。
template <class T, COLORREF t_crBrushColor> class CPaintBkgnd : public CMessageMap { 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有两个模板参数:使用CPaintBkgnd的派生类的名字和用来画窗口背景的颜色。(t_ 前缀通常用来作为模板类的模板参数的前缀)CPaintBkgnd也是从CMessageMap派生的,这并不是必须的,因为所有需要响应消息的类只需使用BEGIN_MSG_MAP宏就足够了,所以你可能看到其他的一些嵌入类的例子代码,它们并不是从该基类派生的。 构造函数和析构函数都相当简单,只是创建和销毁Windows画刷,这个画刷由参数t_crBrushColor决定颜色。接着是消息映射链,它响应WM_ERASEBKGND消息,最后由响应函数OnEraseBkgnd()用构造函数创建的画刷填充窗口的背景。在OnEraseBkgnd()中有两件事需要注意,一个是它使用了一个派生的窗口类的方法(即GetClientRect()),我们如何知道派生类中有GetClientRect()方法呢?如果派生类中没有这个方法我们的代码也不会有任何抱怨,由编译器确认派生类T是从CWindow派生的。另一个是OnEraseBkgnd()没有将消息参数wParam展开为设备上下文(DC)。(WTL最终会解决这个问题,我们很快就可以看到,我保证)
要在我们的窗口中使用这个嵌入类需要做两件事:首先,将它加入到继承列表:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>, public CPaintBkgnd<CMyWindow, RGB(0,0,255)> |
其次,需要CMyWindow将消息传递给CPaintBkgnd,就是将其链入到消息映射链,在CMyWindow的消息映射链中加入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() ... }; |
任何CMyWindow没有处理的消息都被传递给CPaintBkgnd。应该注意的是WM_CLOSE,WM_DESTROY和IDC_ABOUT消息将不会传递,因为这些消息一旦被处理消息映射链的查找就会中止。使用typedef是必要地,因为宏是预处理宏,只能有一个参数,如果我们将CPaintBkgnd<CMyWindow, RGB(0,0,255)>作为参数传递,那个“,”会使预处理器认为我们使用了多个参数。
你可以在继承列表中使用多个嵌入类,每一个嵌入类使用一个CHAIN_MSG_MAP宏,这样消息映射链就会将消息传递给它。这与MFC不同,MFC地CWnd派生类只能有一个基类,MFC自动将消息传递给基类。
ATL程序的结构
到目前为止我们已经有了一个完整地主窗口类(即使不完全有用),让我们看看如何在程序中使用它。一个ATL程序包含一个CComModule类型的全局变量_Module,这和MFC的程序都有一个CWinApp类型的全局变量theApp有些类似,唯一不同的是在ATL中这个变量必须命名为_Module。
下面是stdafx.h文件的开始部分:
// stdafx.h: #define STRICT #define VC_EXTRALEAN
#include <atlbase.h> // 基本的ATL类 extern CComModule _Module; // 全局_Module #include <atlwin.h> // ATL窗口类 |
atlbase.h已经包含最基本的Window编程(www.cppentry.com)的头文件,所以我们不需要在包含windows.h,tchar.h之类的头文件。在CPP文件中声明了_Module变量:
// main.cpp: CComModule _Module; |
CComModule含有程序的初始化和关闭函数,需要在WinMain()中显示的调用,让我们从这里开始:
// main.cpp: CComModule _Module;
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR szCmdLine, int nCmdShow) { _Module.Init(NULL, hInst); _Module.Term(); } |
Init()的第一个参数只有COM的服务程序才有用,由于我们的EXE不含有COM对象,我们只需将NULL传递给Init()就行了。ATL不提供自己的WinMain()和类似MFC的消息泵,所以我们需要创建CMyWindow对象并添加消息泵才能使我们的程序运行。
// 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; } |
上面的代码唯一需要说明的是CWindow::rcDefault,这是CWindow中的成员(静态数据成员),数据类型是RECT。和调用CreateWindow() API时使用CW_USEDEFAULT指定窗口的宽度和高度一样,ATL使用rcDefault作为窗口的最初大小。
在ATL代码内部,ATL使用了一些类似汇编语言的魔法将主窗口的句柄与相应的CMyWindow对象联系起来,在外部看来就是可以毫无问题的在线程之间传递CWindow对象,而MFC的CWnd却不能这样作。
这就是我们的窗口:
我得承认这确实没有什么激动人心的地方。我们将添加一个About菜单并显示一个对话框,主要是为它增加一些情趣。
|