WTL for MFC Programmers
本文章總結(jié)自 這篇文章
本章內(nèi)容
- ATL 背景知識(shí)
- ATL 窗口類(lèi)
- ATL 窗口實(shí)現(xiàn)
- ATL 對(duì)話框?qū)崿F(xiàn)
ATL 背景知識(shí)
WTL 是構(gòu)建于 ATL 之上的一系列附加類(lèi)号显。要學(xué)習(xí) WTL 首先得對(duì) ATL 進(jìn)行一些介紹弛房。
ATL 和 WTL 的發(fā)展歷史
Active Template Library(活動(dòng)模板庫(kù)), 是為了方便進(jìn)行 COM 組件和 ActiveX 控件開(kāi)發(fā)而誕生的。由于 ATL 是為了開(kāi)發(fā) COM 而存在的须床,所以只提供了非常簡(jiǎn)單的界面類(lèi)。直接用 ATL 開(kāi)發(fā)界面程序是比較繁瑣的。所以才會(huì)在此之上封裝 WTL 來(lái)方便開(kāi)發(fā)界面程序。
ATL 風(fēng)格的模版
class CMyWnd : public CWindowImpl<CMyWnd>
{
// do something ...
};
上面的代碼初看可能覺(jué)得很奇怪咆爽,為啥 CMyWnd 繼承了 CWindowImpl, CWindowImpl 又拿 CMyWnd 當(dāng)模版?這么做不會(huì)報(bào)錯(cuò)嗎置森?這么做有什么作用斗埂?
首先,這樣做不會(huì)報(bào)錯(cuò)凫海,因?yàn)?C++ 的語(yǔ)法解釋說(shuō)即使 CMyWnd 類(lèi)只是被部分定義呛凶,類(lèi)名 CMyWnd 已經(jīng)被列入遞歸繼承列表,是可以使用的行贪。
下面的例子解釋了這種寫(xiě)法如何工作:
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>
{
// 沒(méi)有覆寫(xiě)任何函數(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;
}
上述代碼實(shí)現(xiàn)了類(lèi)似于“虛函數(shù)”的多態(tài)功能漾稀。
通過(guò)這種模版寫(xiě)法模闲, D2 繼承的 B1.SayHi 函數(shù),實(shí)際上被解釋成:
void B1<D2>::SayHi()
{
D2* pT = static_cast<D2*>(this);
pT->PrintClassName();
}
SayHi 調(diào)用的是 D2 的 PrintClassName 方法崭捍。
如果不使用這種模版寫(xiě)法尸折,那么 B1 的 SayHi 函數(shù)在調(diào)用 PrintClassName 的時(shí)候,只能去調(diào)用 B1 自己的 PrintClassName 函數(shù)殷蛇,無(wú)法做到調(diào)用 D2 覆寫(xiě)后的 PrintClassName 函數(shù)实夹。
這樣做的好處如下:
- 不需要使用指向?qū)ο蟮闹羔槪梢灾苯邮褂脤?duì)象來(lái)調(diào)用多態(tài)接口粒梦;
- 節(jié)省內(nèi)存亮航,因?yàn)椴恍枰摵瘮?shù)表;
- 因?yàn)闆](méi)有虛函數(shù)表所以不會(huì)發(fā)生在運(yùn)行時(shí)調(diào)用空指針指向的虛函數(shù)谍倦;
- 所有的函數(shù)在編譯時(shí)確定(區(qū)別于 C++ 的虛函數(shù)機(jī)制塞赂,在運(yùn)行時(shí)確定調(diào)用哪個(gè)函數(shù))泪勒。有利于編譯程序?qū)Υa的優(yōu)化昼蛀;
回到最初的代碼:
class CMyWnd : public CWindowImpl<CMyWnd>
{
// do something ...
};
這種寫(xiě)法的作用也就可以理解了。 CMyWnd 中覆寫(xiě)的函數(shù)圆存,將能夠以類(lèi)似多態(tài)的方式被 CWindowImpl 正確調(diào)用叼旋。并且節(jié)省了虛函數(shù)表帶來(lái)的內(nèi)存開(kāi)銷(xiāo)。
ATL 窗口類(lèi)
CWindow:
封裝了所有對(duì) HWND 的操作沦辙,幾乎所有以 HWND 為第一個(gè)參數(shù)的窗口 API 都經(jīng)過(guò)了 CWindow 的封裝夫植。 CWindow 類(lèi)有一個(gè)公有成員 m_hWnd 使你可以直接對(duì)窗口進(jìn)行操作。
CWindowImpl:
繼承自 CWindow, 使用它可以對(duì)窗口消息進(jìn)行處理油讯,從而使窗口具有不同通過(guò)的功能和表現(xiàn)详民。另外它還封裝了 窗口類(lèi)的注冊(cè),窗口的子類(lèi)化 等功能陌兑。
CAxWindow:
繼承自 CWindow, 用于實(shí)現(xiàn)含有 ActiveX 控件的窗口沈跨;
CDialogImpl:
繼承自 CWindow, 用于實(shí)現(xiàn)普通的對(duì)話框;
CAxDialogImpl:
繼承自 CWindow, 用于實(shí)現(xiàn)含有 ActiveX 控件的對(duì)話框兔综;
ATL 窗口實(shí)現(xiàn):
要實(shí)現(xiàn)一個(gè) ATL 窗口饿凛,要按照如下的步驟:
-
在 stdafx.h 中添加 ATL 相關(guān)的頭文件:
#include <atlbase.h> // 基本 ATL 類(lèi) extern CComModule _Module; // 全局 _Module #include <atlwin.h> // 窗口 ATL 類(lèi)
-
在 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 // 在這里進(jìn)行 ATL 窗口的創(chuàng)建、消息泵的創(chuàng)建 ... _Module.Term(); // 結(jié)束 _Module return 0; }
一個(gè) ATL 程序包含一個(gè) CComModule 類(lèi)型的全局變量 _Module, 這和 MFC 程序都有一個(gè) CWinApp 類(lèi)型的全局變量 theApp 有點(diǎn)兒類(lèi)似软驰,唯一不同的是在 ATL 中這個(gè)變量必須被命名為 _Module.
_Module 在 main.cpp 中定義并初始化涧窒,并通過(guò) extern 關(guān)鍵字在 stdafx.h 文件中聲明,其他 #include "stdafx.h" 的模塊就可以使用 _Module 來(lái)進(jìn)行一些操作锭亏。 -
在 MyWindow.h 中定義自己的窗口 CMyWindow:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits> { public: DECLARE_WND_CLASS(_T("My Window Class")) // 指定窗口類(lèi)名 // 消息映射表 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ù)中的第一個(gè)纠吴,這樣寫(xiě)的原因之前已經(jīng)解釋過(guò),是為了實(shí)現(xiàn)類(lèi)似“多態(tài)”的效果慧瘤;
模板參數(shù)中的第二個(gè)戴已,目前不知道原因膳凝;
模板參數(shù)中的第三個(gè),用于指定窗口類(lèi)型恭陡,如WS_OVERLAPPEDWINDOW
,WS_EX_APPWINDOW
等蹬音,CFrameWinTraits
是 ATL 預(yù)先定義的特殊類(lèi)型,你也可以自己定義:typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits; class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
-
在 main.cpp 中使用 CMyWindow 類(lèi)創(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 對(duì)象 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 對(duì)話框?qū)崿F(xiàn):
要實(shí)現(xiàn)一個(gè) ATL 對(duì)話框休玩,和生成 ATL 窗口的方式差不多著淆,只有兩點(diǎn)不同:
窗口的基類(lèi)是 CDialogImpl 而不是 CWindowImpl;
-
你需要在對(duì)話框類(lèi)中定義名稱為 IDD 的公有成員用來(lái)保存對(duì)話框資源的 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; } };