WTL for MFC Programmers
本文章總結自 這篇文章
本章內容
- ATL 背景知識
- ATL 窗口類
- ATL 窗口實現(xiàn)
- ATL 對話框實現(xiàn)
ATL 背景知識
WTL 是構建于 ATL 之上的一系列附加類。要學習 WTL 首先得對 ATL 進行一些介紹。
ATL 和 WTL 的發(fā)展歷史
Active Template Library(活動模板庫), 是為了方便進行 COM 組件和 ActiveX 控件開發(fā)而誕生的。由于 ATL 是為了開發(fā) COM 而存在的,所以只提供了非常簡單的界面類。直接用 ATL 開發(fā)界面程序是比較繁瑣的。所以才會在此之上封裝 WTL 來方便開發(fā)界面程序。
ATL 風格的模版
class CMyWnd : public CWindowImpl<CMyWnd>
{
// do something ...
};
上面的代碼初看可能覺得很奇怪,為啥 CMyWnd 繼承了 CWindowImpl, CWindowImpl 又拿 CMyWnd 當模版?這么做不會報錯嗎?這么做有什么作用?
首先,這樣做不會報錯,因為 C++ 的語法解釋說即使 CMyWnd 類只是被部分定義,類名 CMyWnd 已經被列入遞歸繼承列表,是可以使用的。
下面的例子解釋了這種寫法如何工作:
template <class T>
class B1
{
public:
void SayHi()
{
T* pT = static_cast<T*>(this);
pT->PrintClassName();
}
void PrintClassName() { printf("This is B1\n"); }
};
class D1 : public B1<D1>
{
// 沒有覆寫任何函數(shù)
};
class D2 : public B1<D2>
{
public:
void PrintClassName() { printf("This is D2\n"); }
};
int main()
{
D1 d1;
D2 d2;
d1.SayHi(); // This is B1
d2.SayHi(); // This is D2
return 0;
}
上述代碼實現(xiàn)了類似于“虛函數(shù)”的多態(tài)功能。
通過這種模版寫法, D2 繼承的 B1.SayHi 函數(shù),實際上被解釋成:
void B1<D2>::SayHi()
{
D2* pT = static_cast<D2*>(this);
pT->PrintClassName();
}
SayHi 調用的是 D2 的 PrintClassName 方法。
如果不使用這種模版寫法,那么 B1 的 SayHi 函數(shù)在調用 PrintClassName 的時候,只能去調用 B1 自己的 PrintClassName 函數(shù),無法做到調用 D2 覆寫后的 PrintClassName 函數(shù)。
這樣做的好處如下:
- 不需要使用指向對象的指針,可以直接使用對象來調用多態(tài)接口;
- 節(jié)省內存,因為不需要虛函數(shù)表;
- 因為沒有虛函數(shù)表所以不會發(fā)生在運行時調用空指針指向的虛函數(shù);
- 所有的函數(shù)在編譯時確定(區(qū)別于 C++ 的虛函數(shù)機制,在運行時確定調用哪個函數(shù))。有利于編譯程序對代碼的優(yōu)化;
回到最初的代碼:
class CMyWnd : public CWindowImpl<CMyWnd>
{
// do something ...
};
這種寫法的作用也就可以理解了。 CMyWnd 中覆寫的函數(shù),將能夠以類似多態(tài)的方式被 CWindowImpl 正確調用。并且節(jié)省了虛函數(shù)表帶來的內存開銷。
ATL 窗口類
CWindow:
封裝了所有對 HWND 的操作,幾乎所有以 HWND 為第一個參數(shù)的窗口 API 都經過了 CWindow 的封裝。 CWindow 類有一個公有成員 m_hWnd 使你可以直接對窗口進行操作。
CWindowImpl:
繼承自 CWindow, 使用它可以對窗口消息進行處理,從而使窗口具有不同通過的功能和表現(xiàn)。另外它還封裝了 窗口類的注冊,窗口的子類化 等功能。
CAxWindow:
繼承自 CWindow, 用于實現(xiàn)含有 ActiveX 控件的窗口;
CDialogImpl:
繼承自 CWindow, 用于實現(xiàn)普通的對話框;
CAxDialogImpl:
繼承自 CWindow, 用于實現(xiàn)含有 ActiveX 控件的對話框;
ATL 窗口實現(xiàn):
要實現(xiàn)一個 ATL 窗口,要按照如下的步驟:
-
在 stdafx.h 中添加 ATL 相關的頭文件:
#include <atlbase.h> // 基本 ATL 類 extern CComModule _Module; // 全局 _Module #include <atlwin.h> // 窗口 ATL 類
-
在 main.cpp 中定義 CComModule _Module 并初始化它:
#include "stdafx.h" CComModule _Module; int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { _Module.Init(NULL, hInstance); // 初始化 _Module // 在這里進行 ATL 窗口的創(chuàng)建、消息泵的創(chuàng)建 ... _Module.Term(); // 結束 _Module return 0; }
一個 ATL 程序包含一個 CComModule 類型的全局變量 _Module, 這和 MFC 程序都有一個 CWinApp 類型的全局變量 theApp 有點兒類似,唯一不同的是在 ATL 中這個變量必須被命名為 _Module.
_Module 在 main.cpp 中定義并初始化,并通過 extern 關鍵字在 stdafx.h 文件中聲明,其他 #include "stdafx.h" 的模塊就可以使用 _Module 來進行一些操作。 -
在 MyWindow.h 中定義自己的窗口 CMyWindow:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits> { public: DECLARE_WND_CLASS(_T("My Window Class")) // 指定窗口類名 // 消息映射表 BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_CLOSE, OnClose) // 在這里將消息映射到函數(shù) 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; } };
注意第一行代碼
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
模板參數(shù)中的第一個,這樣寫的原因之前已經解釋過,是為了實現(xiàn)類似“多態(tài)”的效果;
模板參數(shù)中的第二個,目前不知道原因;
模板參數(shù)中的第三個,用于指定窗口類型,如WS_OVERLAPPEDWINDOW
,WS_EX_APPWINDOW
等,CFrameWinTraits
是 ATL 預先定義的特殊類型,你也可以自己定義:typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits; class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
-
在 main.cpp 中使用 CMyWindow 類創(chuàng)建主窗口:
#include "stdafx.h" #include "MyWindow.h" CComModule _Module; int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { _Module.Init(NULL, hInstance); // 聲明 CMyWindow 對象 CMyWindow wndMain; // 創(chuàng)建窗口 if (NULL == wndMain.Create(NULL, CWindow::rcDefault, _T("My First ATL Window"))) { return 1; } wndMain.ShowWindow(nCmdShow); wndMain.UpdateWindow(); // 消息泵 MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } _Module.Term(); return msg.wParam; }
ATL 對話框實現(xiàn):
要實現(xiàn)一個 ATL 對話框,和生成 ATL 窗口的方式差不多,只有兩點不同:
窗口的基類是 CDialogImpl 而不是 CWindowImpl;
-
你需要在對話框類中定義名稱為 IDD 的公有成員用來保存對話框資源的 ID;
#include "resource.h" class CAboutDlg : public CDialogImpl<CAboutDlg> { public: enum { IDD = IDD_ABOUT }; BEGIN_MSG_MAP(CAboutDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitControl) MESSAGE_HANDLER(WM_CLOSE, OnClose) COMMAND_ID_HANDLER(IDOK, OnOkCancel) COMMAND_ID_HANDLER(IDCANCEL, OnOkCancel) END_MSG_MAP() LRESULT OnInitControl(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { CenterWindow(); return TRUE; } LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { EndDialog(0); return 0; } LRESULT OnOkCancel(WORD wNotifyCode, WORD wID, HWND hWndCtrl, BOOL& bHandled) { EndDialog(wID); return 0; } };