README.TXT
在你開(kāi)始使用WTL或著在本文章的討論區(qū)張貼消息之前,我想請(qǐng)你先閱讀下面的材料。
你需要開(kāi)發(fā)平臺(tái)SDK(Platform SDK)。你要使用WTL不能沒(méi)有它,你可以使用在線升級(jí)安裝開(kāi)發(fā)平臺(tái)SDK,也可以下載全部文件后在本地安裝。在使用之前要將SDK的包含文件(.h頭文件)和庫(kù)文件(.Lib文件)路徑添加到VC的搜索目錄,SDK有現(xiàn)成的工具完成這個(gè)工作,這個(gè)工具位于開(kāi)發(fā)平臺(tái)SDK程序組的“Visual Studio Registration”文件夾里。
你需要安裝 WTL。你可以從微軟的網(wǎng)站上下載WTL的7.0版,(*編輯注:WTL也可以到VC知識(shí)庫(kù)http://www.vckbase.com/tools下載) 在安裝之前可以先查看“Introduction to WTL - Part 1”和“Easy installation of WTL”這兩篇文章,了解一下所要安裝的文件的信息,雖然現(xiàn)在這些文章有些過(guò)時(shí),但還是可以提供很多有用的信息。有一件我認(rèn)為不該在本篇文章中提到的事是告訴VC如何搜索WTL的包含文件路徑,如果你用的VC6,用鼠標(biāo)點(diǎn)擊 Tools\Options,轉(zhuǎn)到Directories標(biāo)簽頁(yè),在顯示路徑的列表框中選擇Include Files,然后將WTL的包含文件的存放路徑添加到包含文件搜索路徑列表中。
你需要了解MFC。很好地了解MFC將有助于你理解后面提到的有關(guān)消息映射的宏并能夠編輯那些標(biāo)有“不要編輯(DO NOT EDIT)”的代碼而不會(huì)出現(xiàn)問(wèn)題。
你需要清楚地知道如何使用Win32 API編程。如果你是直接從MFC開(kāi)始學(xué)習(xí)Windows編程,沒(méi)有學(xué)過(guò)API級(jí)別的消息處理方式,那很不幸你會(huì)在使用WTL時(shí)遇到麻煩。如果不了解Windows消息中WPARAM參數(shù)和LPARAM參數(shù)的意義,應(yīng)該明白需要讀一些這方面的文章(在CodeProject有大量的此類文章)。
你需要知道 C++ 模板的語(yǔ)法,你可以到VC Forum FAQ 相關(guān)的連接尋求答案。
我只是討論了一些涵蓋VC 6的特點(diǎn),不過(guò)據(jù)我了解所有的程序都可以在VC 7上使用。由于我不使用VC 7,我無(wú)法對(duì)那些在VC 7中出現(xiàn)的問(wèn)題提供幫助,不過(guò)你還是可以放心的在此張貼你的問(wèn)題,因?yàn)槠渌娜丝赡軙?huì)幫助你。
對(duì)本系列文章的總體介紹
WTL 具有兩面性,確實(shí)是這樣的。它沒(méi)有MFC的界面(GUI)類庫(kù)那樣功能強(qiáng)大,但是能夠生成很小的可執(zhí)行文件。如果你象我一樣使用MFC進(jìn)行界面編程,你會(huì)覺(jué)得MFC提供的界面控件封裝使用起來(lái)非常舒服,更不用說(shuō)MFC內(nèi)置的消息處理機(jī)制。當(dāng)然,如果你也象我一樣不希望自己的程序僅僅因?yàn)槭褂昧薓FC的框架就增加幾百K的大小的話,WTL就是你的選擇。當(dāng)然,我們還要克服一些障礙:
- ATL樣式的模板類初看起來(lái)有點(diǎn)怪異
- 沒(méi)有類向?qū)У闹С?,所以要手工處理所有的消息映射?
- MSDN沒(méi)有正式的文檔支持,你需要到處去收集有關(guān)的文檔,甚至是查看WTL的源代碼。
- 買不到參考書籍
- 沒(méi)有微軟的官方支持
- ATL/WTL的窗口與MFC的窗口有很大的不同,你所了解的有關(guān)MFC的知識(shí)并不全部適用與WTL。
從另一方面講,WTL也有它自身的優(yōu)勢(shì):
- 不需要學(xué)習(xí)或掌握復(fù)雜的文檔/視圖框架。
- 具有MFC的基本的界面特色,比如DDX/DDV和命令狀態(tài)的自動(dòng)更新功能(譯者加:比如菜單的Check標(biāo)記和Enable標(biāo)記)。
- 增強(qiáng)了一些MFC的特性(比如更加易用的分隔窗口)。
- 可生成比靜態(tài)鏈接的MFC程序更小的可執(zhí)行文件(譯者加:WTL的所有源代碼都是靜態(tài)鏈接到你的程序中的)。
- 你可以修正自己使用的WTL中的錯(cuò)誤(BUG)而不會(huì)影響其他的應(yīng)用程序(相比之下,如果你修正了有BUG的MFC/CRT動(dòng)態(tài)庫(kù)就可能會(huì)引起其它應(yīng)用程序的崩潰。
- 如果你仍然需要使用MFC,MFC的窗口和ATL/WTL的窗口可以“和平共處”。(例如我工作中的一個(gè)原型就使用了了MFC的CFrameWnd,并在其內(nèi)包含了WTL的CSplitterWindow,在CSplitterWindow中又使用了MFC的CDialogs -- 我并不是為了炫耀什么,只是修改了MFC的代碼使之能夠使用WTL的分割窗口,它比MFC的分割窗口好的多)。
在這一系列文章中,我將首先介紹ATL的窗口類,畢竟WTL是構(gòu)建與ATL之上的一系列附加類,所以需要很好的了解ATL的窗口類。介紹完ATL之后我將介紹WTL的特性以并展示它是如何使界面編程變得輕而易舉。
對(duì)第一章的簡(jiǎn)單介紹
WTL是個(gè)很酷的工具,在理解這一點(diǎn)之前需要首先介紹ATL。WTL是構(gòu)建與ATL之上的一系列附加類,如果你是個(gè)嚴(yán)格使用MFC的程序員那么你可能沒(méi)有機(jī)會(huì)接觸到ATL的界面類,所以請(qǐng)容忍我在開(kāi)始WTL之前先羅索一些別的東西,繞道來(lái)介紹一下ATL是很有必要地。
在本文的第一部分,我將給出一點(diǎn)ATL的背景知識(shí),包括一些編寫ATL代碼必須知道的基本知識(shí),快速的解釋一些令人不知所措的ATL模板類和基本的ATL窗口類。
ATL 背景知識(shí)
ATL 和 WTL 的發(fā)展歷史
“活動(dòng)模板庫(kù)”(Active Template Library)是一個(gè)很古怪的名字,不是嗎?那些年紀(jì)大的人可能還記得它最初被稱為“網(wǎng)絡(luò)組件模板庫(kù)”,這可能是它更準(zhǔn)確的稱呼,因?yàn)锳TL的目的就是使編寫組件對(duì)象和ActiveX控件更容易一些(ATL是在微軟開(kāi)發(fā)新產(chǎn)品ActiveX-某某的過(guò)程中開(kāi)發(fā)的,那些ActiveX-某某現(xiàn)在被稱為某某.NET)。由于ATL是為了便于編寫組件對(duì)象而存在的,所以只提供了簡(jiǎn)單的界面類,相當(dāng)于MFC的窗口類(CWnd)和對(duì)話框類(CDialog)。幸運(yùn)的是這些類非常的靈活,能夠在其基礎(chǔ)上構(gòu)建象WTL這樣的附加類。
WTL現(xiàn)在已經(jīng)是第二次修正了,最初的版本是3.1,現(xiàn)在的版本是7(WTL的版本號(hào)之所以這樣選擇是為了與ATL的版本匹配,所以不存在1和2這樣的版本號(hào))。WTL 3.1可以與VC 6和VC 7一起使用,但是在VC 7下需要定義幾個(gè)預(yù)處理標(biāo)號(hào)。WTL 7向下兼容WTL 3.1,并且不作任何修改就可以與VC 7一起使用,現(xiàn)在看來(lái)沒(méi)有任何理由還使用3.1來(lái)進(jìn)行新的開(kāi)發(fā)工作。
ATL-style 模板
即使你能夠毫不費(fèi)力地閱讀C++的模板類代碼,仍然有兩件事可能會(huì)使你有些頭暈,以下面這個(gè)類的定義為例:
class CMyWnd : public CWindowImpl<CMyWnd>
{
...
};
這樣作是合法的,因?yàn)镃++的語(yǔ)法解釋說(shuō)即使CMyWnd類只是被部分定義,類名CMyWnd已經(jīng)被列入遞歸繼承列表,是可以使用的。將類名作為模板類的參數(shù)是因?yàn)锳TL要做另一件詭秘的事情,那就是編譯期間的虛函數(shù)調(diào)用機(jī)制。
如果你想要了解它是如何工作地,請(qǐng)看下面的例子:
template <class T>
class B1
{
public:
void SayHi()
{
T* pT = static_cast<T*>(this); // HUH?? 我將在下面解釋
pT->PrintClassName();
}
protected:
void PrintClassName() { cout << "This is B1"; }
};
class D1 : public B1<D1>
{
// No overridden functions at all
};
class D2 : public B1<D2>
{
protected:
void PrintClassName() { cout << "This is D2"; }
};
main()
{
D1 d1;
D2 d2;
d1.SayHi(); // prints "This is B1"
d2.SayHi(); // prints "This is D2"
}
這句代碼static_cast<T*>(this) 就是竅門所在。它根據(jù)函數(shù)調(diào)用時(shí)的特殊處理將指向B1類型的指針this指派為D1或D2類型的指針,因?yàn)槟0宕a是在編譯其間生成的,所以只要編譯器生成正確的繼承列表,這樣指派就是安全的。(如果你寫成:
class D3 : public B1<D2>
就會(huì)有麻煩) 之所以安全是因?yàn)閠his對(duì)象只可能是指向D1或D2(在某些情況下)類型的對(duì)象,不會(huì)是其他的東西。注意這很像C++的多態(tài)性(polymorphism),只是SayHi()方法不是虛函數(shù)。
要解釋這是如何工作的,首先看對(duì)每個(gè)SayHi()函數(shù)的調(diào)用,在第一個(gè)函數(shù)調(diào)用,對(duì)象B1被指派為D1,所以代碼被解釋成:
void B1<D1>::SayHi()
{
D1* pT = static_cast<D1*>(this);
pT->PrintClassName();
}
由于D1沒(méi)有重載PrintClassName(),所以查看基類B1,B1有PrintClassName(),所以B1的PrintClassName()被調(diào)用。
現(xiàn)在看第二個(gè)函數(shù)調(diào)用SayHi(),這一次對(duì)象被指派為D2類型,SayHi()被解釋成:
void B1<D2>::SayHi()
{
D2* pT = static_cast<D2*>(this);
pT->PrintClassName();
}
這一次,D2含有PrintClassName()方法,所以D2的PrintClassName()方法被調(diào)用。
這種技術(shù)的有利之處在于:
- 不需要使用指向?qū)ο蟮闹羔槨?
- 節(jié)省內(nèi)存,因?yàn)椴恍枰摵瘮?shù)表。
- 因?yàn)闆](méi)有虛函數(shù)表所以不會(huì)發(fā)生在運(yùn)行時(shí)調(diào)用空指針指向的虛函數(shù)。
- 所有的函數(shù)調(diào)用在編譯時(shí)確定(譯者加:區(qū)別于C++的虛函數(shù)機(jī)制使用的動(dòng)態(tài)編連),有利于編譯程序?qū)Υa的優(yōu)化。
節(jié)省虛函數(shù)表在這個(gè)例子中看起來(lái)無(wú)足輕重(每個(gè)虛函數(shù)只有4個(gè)字節(jié)),但是設(shè)想一下如果有15個(gè)基類,每個(gè)類含有20個(gè)方法,加起來(lái)就相當(dāng)可觀了。
ATL 窗口類
好了,關(guān)于ATL的背景知識(shí)已經(jīng)講的構(gòu)多了,到了該正式講ATL的時(shí)候了。ATL在設(shè)計(jì)時(shí)接口定義和實(shí)現(xiàn)是嚴(yán)格區(qū)分開(kāi)的,這在窗口類的設(shè)計(jì)中是最明顯的,這一點(diǎn)類似于COM,COM的接口定義和實(shí)現(xiàn)是完全分開(kāi)的(或者可能有多個(gè)實(shí)現(xiàn))。
ATL有一個(gè)專門為窗口設(shè)計(jì)的接口,可以做全部的窗口操作,這就是CWindow。它實(shí)際上就是對(duì)HWND操作的包裝類,對(duì)幾乎所有以HWND句柄為第一個(gè)參數(shù)的窗口API的進(jìn)行了封裝,例如:SetWindowText() 和 DestroyWindow()。CWindow類有一個(gè)公有成員m_hWnd,使你可以直接對(duì)窗口的句柄操作,CWindow還有一個(gè)操作符HWND,你可以講CWindow對(duì)象傳遞給以HWND為參數(shù)的函數(shù),但這與CWnd::GetSafeHwnd()(譯者加:MFC的方法)沒(méi)有任何等同之處。
CWindow 與 MFC 的CWnd類有很大的不同,創(chuàng)建一個(gè)CWindow對(duì)象占用很少的資源,因?yàn)橹挥幸粋€(gè)數(shù)據(jù)成員,沒(méi)有MFC窗口中的對(duì)象鏈,MFC內(nèi)部維持這一個(gè)對(duì)象鏈,此對(duì)象鏈將HWND映射到CWnd對(duì)象。還有一點(diǎn)與MFC的CWnd類不同的是當(dāng)一個(gè)CWindow對(duì)象超出了作用域,它關(guān)聯(lián)的窗口并不被銷毀掉,這意味著你并不需要隨時(shí)記得分離你所創(chuàng)建的臨時(shí)CWindow對(duì)象。
在ATL類中對(duì)窗口過(guò)程的實(shí)現(xiàn)是CWindowImpl。CWindowImpl 含有所有窗口實(shí)現(xiàn)代碼,例如:窗口類的注冊(cè),窗口的子類化,消息映射以及基本的WindowProc()函數(shù),可以看出這與MFC的設(shè)計(jì)有很大的不同,MFC將所有的代碼都放在一個(gè)CWnd類中。
還有兩個(gè)獨(dú)立的類包含對(duì)話框的實(shí)現(xiàn),它們分別是CDialogImpl 和 CAxDialogImpl,CDialogImpl 用于實(shí)現(xiàn)普通的對(duì)話框而CAxDialogImpl實(shí)現(xiàn)含有ActiveX控件的對(duì)話框。
定義一個(gè)窗口的實(shí)現(xiàn)
任何非對(duì)話框窗口都是從CWindowImpl 派生的,你的新類需要包含三件事情:
- 一個(gè)窗口類的定義
- 一個(gè)消息映射鏈
- 窗口使用的默認(rèn)窗口類型,稱為window traits
窗口類的定義通過(guò)DECLARE_WND_CLASS宏或DECLARE_WND_CLASS_EX宏來(lái)實(shí)現(xiàn)。這輛個(gè)宏定義了一個(gè)CWndClassInfo結(jié)構(gòu),這個(gè)結(jié)構(gòu)封裝了WNDCLASSEX結(jié)構(gòu)。DECLARE_WND_CLASS宏讓你指定窗口類的類名,其他參數(shù)使用默認(rèn)設(shè)置,而DECLARE_WND_CLASS_EX宏還允許你指定窗口類的類型和窗口的背景顏色,你也可以用NULL作為類名,ATL會(huì)自動(dòng)為你生成一個(gè)類名。
讓我們開(kāi)始定義一個(gè)新類,在后面的章節(jié)我會(huì)逐步的完成這個(gè)類的定義。
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
};
接下來(lái)是消息映射鏈,ATL的消息映射鏈比MFC的簡(jiǎn)單的多,ATL的消息映射鏈被展開(kāi)為switch語(yǔ)句,switch語(yǔ)句正確的消息處理者并調(diào)用相應(yīng)的函數(shù)。使用消息映射鏈的宏是BEGIN_MSG_MAP 和 END_MSG_MAP,讓我們?yōu)槲覀兊拇翱谔砑右粋€(gè)空的消息映射鏈。
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};
我將在下一節(jié)展開(kāi)講如何如何添加消息處理到消息映射鏈。最后,我們需要為我們的窗口類定義窗口的特征,窗口的特征就是窗口類型和擴(kuò)展窗口類型的聯(lián)合體,用于創(chuàng)建窗口時(shí)指定窗口的類型。窗口類型被指定為參數(shù)模板,所以窗口的調(diào)用者不需要為指定窗口的正確類型而煩心,下面是是同ATL類CWinTraits定義窗口類型的例子:
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()
};
調(diào)用者可以重載CMyWindowTraits的類型定義,但是一般情況下這是沒(méi)有必要的,ATL提供了幾個(gè)預(yù)先定義的特殊的類型,其中之一就是CFrameWinTraits,一個(gè)非常棒的框架窗口:
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CFrameWinTraits;
填寫消息映射鏈
ATL的消息映射鏈?zhǔn)菍?duì)開(kāi)發(fā)者不太友好的部分,也是WTL對(duì)其改進(jìn)最大的部分。類向?qū)е辽倏梢宰屇闾砑酉㈨憫?yīng),然而ATL沒(méi)有消息相關(guān)的宏和象MFC那樣的參數(shù)自動(dòng)展開(kāi)功能,在ATL中只有三種類型的消息處理,一個(gè)是WM_NOTIFY,一個(gè)是WM_COMMAND,第三類是其他窗口消息,讓我們開(kāi)始為我們的窗口添加WM_CLOSE 和 WM_DESTROY的消息相應(yīng)函數(shù)。
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;
}
};
你可能注意到消息響應(yīng)函數(shù)的到的是原始的WPARAM 和 LPARAM值,你需要自己將其展開(kāi)為相應(yīng)的消息所需要的參數(shù)。還有第四個(gè)參數(shù)bHandled,這個(gè)參數(shù)在消息相應(yīng)函數(shù)調(diào)用被ATL設(shè)置為TRUE,如果在你的消息響應(yīng)處理完之后需要ATL調(diào)用默認(rèn)的WindowProc()處理該消息,你可以講bHandled設(shè)置為FALSE。這與MFC不同,MFC是顯示的調(diào)用基類的響應(yīng)函數(shù)來(lái)實(shí)現(xiàn)的默認(rèn)的消息處理的。
讓我們也添加一個(gè)對(duì)WM_COMMAND消息的處理,假設(shè)我們的窗口有一個(gè)ID為IDC_ABOUT的About菜單:
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;
}
};
需要注意得是COMMAND_HANDLER宏已經(jīng)將消息的參數(shù)展開(kāi)了,同樣,NOTIFY_HANDLER宏也將WM_NOTIFY消息的參數(shù)展開(kāi)了。
高級(jí)消息映射鏈和嵌入類
ATL的另一個(gè)顯著不同之處就是任何一個(gè)C++類都可以響應(yīng)消息,而MFC只是將消息響應(yīng)任務(wù)分給了CWnd類和CCmdTarget類,外加幾個(gè)有PreTranslateMessage()方法的類。ATL的這種特性允許我們編寫所謂的“嵌入類”,為我們的窗口添加特性只需將該類添加到繼承列表中就行了,就這么簡(jiǎn)單!
一個(gè)基本的帶有消息映射鏈的類通常是模板類,將派生類的類名作為模板的參數(shù),這樣它就可以訪問(wèn)派生類中的成員,比如m_hWnd(CWindow類中的HWND成員)。讓我們來(lái)看一個(gè)嵌入類的例子,這個(gè)嵌入類通過(guò)響應(yīng)WM_ERASEBKGND消息來(lái)畫窗口的背景。
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;
};
讓我們來(lái)研究一下這個(gè)新類。首先,CPaintBkgnd有兩個(gè)模板參數(shù):使用CPaintBkgnd的派生類的名字和用來(lái)畫窗口背景的顏色。(t_ 前綴通常用來(lái)作為模板類的模板參數(shù)的前綴)CPaintBkgnd也是從CMessageMap派生的,這并不是必須的,因?yàn)樗行枰憫?yīng)消息的類只需使用BEGIN_MSG_MAP宏就足夠了,所以你可能看到其他的一些嵌入類的例子代碼,它們并不是從該基類派生的。
構(gòu)造函數(shù)和析構(gòu)函數(shù)都相當(dāng)簡(jiǎn)單,只是創(chuàng)建和銷毀Windows畫刷,這個(gè)畫刷由參數(shù)t_crBrushColor決定顏色。接著是消息映射鏈,它響應(yīng)WM_ERASEBKGND消息,最后由響應(yīng)函數(shù)OnEraseBkgnd()用構(gòu)造函數(shù)創(chuàng)建的畫刷填充窗口的背景。在OnEraseBkgnd()中有兩件事需要注意,一個(gè)是它使用了一個(gè)派生的窗口類的方法(即GetClientRect()),我們?nèi)绾沃琅缮愔杏蠫etClientRect()方法呢?如果派生類中沒(méi)有這個(gè)方法我們的代碼也不會(huì)有任何抱怨,由編譯器確認(rèn)派生類T是從CWindow派生的。另一個(gè)是OnEraseBkgnd()沒(méi)有將消息參數(shù)wParam展開(kāi)為設(shè)備上下文(DC)。(WTL最終會(huì)解決這個(gè)問(wèn)題,我們很快就可以看到,我保證)
要在我們的窗口中使用這個(gè)嵌入類需要做兩件事:首先,將它加入到繼承列表:
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沒(méi)有處理的消息都被傳遞給CPaintBkgnd。應(yīng)該注意的是WM_CLOSE,WM_DESTROY和IDC_ABOUT消息將不會(huì)傳遞,因?yàn)檫@些消息一旦被處理消息映射鏈的查找就會(huì)中止。使用typedef是必要地,因?yàn)楹晔穷A(yù)處理宏,只能有一個(gè)參數(shù),如果我們將CPaintBkgnd<CMyWindow, RGB(0,0,255)>作為參數(shù)傳遞,那個(gè)“,”會(huì)使預(yù)處理器認(rèn)為我們使用了多個(gè)參數(shù)。
你可以在繼承列表中使用多個(gè)嵌入類,每一個(gè)嵌入類使用一個(gè)CHAIN_MSG_MAP宏,這樣消息映射鏈就會(huì)將消息傳遞給它。這與MFC不同,MFC地CWnd派生類只能有一個(gè)基類,MFC自動(dòng)將消息傳遞給基類。
ATL程序的結(jié)構(gòu)
到目前為止我們已經(jīng)有了一個(gè)完整地主窗口類(即使不完全有用),讓我們看看如何在程序中使用它。一個(gè)ATL程序包含一個(gè)CComModule類型的全局變量_Module,這和MFC的程序都有一個(gè)CWinApp類型的全局變量theApp有些類似,唯一不同的是在ATL中這個(gè)變量必須命名為_(kāi)Module。
下面是stdafx.h文件的開(kāi)始部分:
// stdafx.h:
#define STRICT
#define VC_EXTRALEAN
#include <atlbase.h> // 基本的ATL類
extern CComModule _Module; // 全局_Module
#include <atlwin.h> // ATL窗口類
atlbase.h已經(jīng)包含最基本的Window編程的頭文件,所以我們不需要在包含windows.h,tchar.h之類的頭文件。在CPP文件中聲明了_Module變量:
// main.cpp:
CComModule _Module;
CComModule含有程序的初始化和關(guān)閉函數(shù),需要在WinMain()中顯示的調(diào)用,讓我們從這里開(kāi)始:
// main.cpp:
CComModule _Module;
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, int nCmdShow)
{
_Module.Init(NULL, hInst);
_Module.Term();
}
Init()的第一個(gè)參數(shù)只有COM的服務(wù)程序才有用,由于我們的EXE不含有COM對(duì)象,我們只需將NULL傳遞給Init()就行了。ATL不提供自己的WinMain()和類似MFC的消息泵,所以我們需要?jiǎng)?chuàng)建CMyWindow對(duì)象并添加消息泵才能使我們的程序運(yùn)行。
// 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;
}
上面的代碼唯一需要說(shuō)明的是CWindow::rcDefault,這是CWindow中的成員(靜態(tài)數(shù)據(jù)成員),數(shù)據(jù)類型是RECT。和調(diào)用CreateWindow() API時(shí)使用CW_USEDEFAULT指定窗口的寬度和高度一樣,ATL使用rcDefault作為窗口的最初大小。
在ATL代碼內(nèi)部,ATL使用了一些類似匯編語(yǔ)言的魔法將主窗口的句柄與相應(yīng)的CMyWindow對(duì)象聯(lián)系起來(lái),在外部看來(lái)就是可以毫無(wú)問(wèn)題的在線程之間傳遞CWindow對(duì)象,而MFC的CWnd卻不能這樣作。
這就是我們的窗口:
我得承認(rèn)這確實(shí)沒(méi)有什么激動(dòng)人心的地方。我們將添加一個(gè)About菜單并顯示一個(gè)對(duì)話框,主要是為它增加一些情趣。
ATL中的對(duì)話框
我們前面提到過(guò),ATL有兩個(gè)對(duì)話框類,我們的About對(duì)話框使用CDialogImpl。生成一個(gè)新對(duì)話框和生成一個(gè)主窗口幾乎一樣,只有兩點(diǎn)不同:
- 窗口的基類是CDialogImpl而不是CWindowImpl。
- 你需要定義名稱為IDD的公有成員用來(lái)保存對(duì)話框資源的ID。
現(xiàn)在開(kāi)始為About對(duì)話框定義一個(gè)新類:
class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
END_MSG_MAP()
};
ATL沒(méi)有在內(nèi)部實(shí)現(xiàn)對(duì)“OK”和“Cancel”兩個(gè)按鈕的響應(yīng)處理,所以我們需要自己添加這些代碼,如果用戶用鼠標(biāo)點(diǎn)擊標(biāo)題欄的關(guān)閉按鈕,WM_CLOSE的響應(yīng)函數(shù)就會(huì)被調(diào)用。我們還需要處理WM_INITDIALOG消息,這樣我們就能夠在對(duì)話框出現(xiàn)時(shí)正確的設(shè)置鍵盤焦點(diǎn),下面是完整的類定義和消息響應(yīng)函數(shù)。
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;
}
};
我使用一個(gè)消息響應(yīng)函數(shù)同時(shí)處理“OK”和“Cancel”兩個(gè)按鈕的WM_COMMAND消息,因?yàn)槊铐憫?yīng)函數(shù)的wID參數(shù)就已經(jīng)指明了消息是來(lái)自“OK”按鈕還是來(lái)自“Cancel”按鈕。
顯示對(duì)話框的方法與MFC相似,創(chuàng)建一個(gè)新對(duì)話框類的實(shí)例,然后調(diào)用DoModal()方法?,F(xiàn)在我們返回主窗口,添加一個(gè)帶有About菜單項(xiàng)的菜單用來(lái)顯示我們的對(duì)話框,這需要再添加兩個(gè)消息響應(yīng)函數(shù),一個(gè)是響應(yīng)WM_CREATE,另一個(gè)是響應(yīng)菜單的IDC_ABOUT命令。
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(),
MAKEINTRESOURCE(IDR_MENU1) );
SetMenu ( hmenu );
return 0;
}
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CAboutDlg dlg;
dlg.DoModal();
return 0;
}
// ...
};
在指定對(duì)話框的父窗口的方式上有些不同,MFC是通過(guò)構(gòu)造函數(shù)將父窗口的指針傳遞給對(duì)話框而在ATL中是將父窗口的指針作為DoModal()方法的第一個(gè)參數(shù)傳遞給對(duì)話框的,如果象上面的代碼一樣沒(méi)有指定父窗口,ATL會(huì)使用GetActiveWindow()得到的窗口(也就是我們的主框架窗口)作為對(duì)話框的父窗口。
對(duì)LoadMenu()方法的調(diào)用展示了CComModule的另一個(gè)方法-GetResourceInstance(),它返回你的EXE的HINSTANCE實(shí)例,和MFC的AfxGetResourceHandle()方法相似。(當(dāng)然還有CComModule::GetModuleInstance(),它相當(dāng)于MFC的AfxGetInstanceHandle()。)
這就是主窗口和對(duì)話框的顯示效果:
我會(huì)繼續(xù)講WTL,我保證!
我會(huì)繼續(xù)講WTL的,只是會(huì)在第二部分。我覺(jué)得既然這些文章是寫給使用MFC的開(kāi)發(fā)者的,所以有必要在投入WTL之前先介紹一些ATL。如果你是第一次接觸到ATL,那現(xiàn)在你就可以嘗試寫一些小程序,處理消息和使用嵌入類,你也可以嘗試用類向?qū)еС窒⒂成滏?,使它能夠自?dòng)添加消息響應(yīng)。現(xiàn)在就開(kāi)始,右鍵單擊CMyWindow項(xiàng),在彈出的上下文菜單中單擊“Add Windows Message Handler”菜單項(xiàng)。
在第二部分,我將全面介紹基本的WTL窗口類和一個(gè)更好的消息映射宏。
posted on 2007-03-09 11:46
jay 閱讀(420)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
WTL