在你開始使用WTL或著在本文章的討論區張貼消息之前,我想請你先閱讀下面的材料。
你需要開發平台SDK(Platform SDK)。你要使用WTL不能沒有它,你可以使用在線升級安裝開發平台SDK,也可以下載全部文件後在本地安裝。在使用之前要將SDK的包含文件(.h頭文件)和庫文件(.Lib文件)路徑添加到VC的搜索目錄,SDK有現成的工具完成這個工作,這個工具位於開發平台SDK程序組的“Visual Studio Registration”文件夾裡。
你需要安裝 WTL。你可以從微軟的網站上下載WTL的7.0版,(*編輯注:WTL也可以到VC知識庫http://www.vckbase.com/tools下載) 在安裝之前可以先查看“Introduction to WTL - Part 1”和“Easy installation of WTL”這兩篇文章,了解一下所要安裝的文件的信息,雖然現在這些文章有些過時,但還是可以提供很多有用的信息。有一件我認為不該在本篇文章中提到的事是告訴VC如何搜索WTL的包含文件路徑,如果你用的VC6,用鼠標點擊 Tools\Options,轉到Directories標簽頁,在顯示路徑的列表框中選擇Include Files,然後將WTL的包含文件的存放路徑添加到包含文件搜索路徑列表中。
你需要了解MFC。很好地了解MFC將有助於你理解後面提到的有關消息映射的宏並能夠編輯那些標有“不要編輯(DO NOT EDIT)”的代碼而不會出現問題。
你需要清楚地知道如何使用Win32 API編程。如果你是直接從MFC開始學習Windows編程,沒有學過API級別的消息處理方式,那很不幸你會在使用WTL時遇到麻煩。如果不了解Windows消息中WPARAM參數和LPARAM參數的意義,應該明白需要讀一些這方面的文章(在CodeProject有大量的此類文章)。
你需要知道 C++ 模板的語法,你可以到VC Forum FAQ 相關的連接尋求答案。
我只是討論了一些涵蓋VC 6的特點,不過據我了解所有的程序都可以在VC 7上使用。由於我不使用VC 7,我無法對那些在VC 7中出現的問題提供幫助,不過你還是可以放心的在此張貼你的問題,因為其他的人可能會幫助你。
對本系列文章的總體介紹WTL 具有兩面性,確實是這樣的。它沒有MFC的界面(GUI)類庫那樣功能強大,但是能夠生成很小的可執行文件。如果你象我一樣使用MFC進行界面編程,你會覺得MFC提供的界面控件封裝使用起來非常舒服,更不用說MFC內置的消息處理機制。當然,如果你也象我一樣不希望自己的程序僅僅因為使用了MFC的框架就增加幾百K的大小的話,WTL就是你的選擇。當然,我們還要克服一些障礙:
從另一方面講,WTL也有它自身的優勢:
在這一系列文章中,我將首先介紹ATL的窗口類,畢竟WTL是構建與ATL之上的一系列附加類,所以需要很好的了解ATL的窗口類。介紹完ATL之後我將介紹WTL的特性以並展示它是如何使界面編程變得輕而易舉。
對第一章的簡單介紹WTL是個很酷的工具,在理解這一點之前需要首先介紹ATL。WTL是構建與ATL之上的一系列附加類,如果你是個嚴格使用MFC的程序員那麼你可能沒有機會接觸到ATL的界面類,所以請容忍我在開始WTL之前先羅索一些別的東西,繞道來介紹一下ATL是很有必要地。
在本文的第一部分,我將給出一點ATL的背景知識,包括一些編寫ATL代碼必須知道的基本知識,快速的解釋一些令人不知所措的ATL模板類和基本的ATL窗口類。
ATL 背景知識 ATL 和 WTL 的發展歷史“活動模板庫”(Active Template Library)是一個很古怪的名字,不是嗎?那些年紀大的人可能還記得它最初被稱為“網絡組件模板庫”,這可能是它更准確的稱呼,因為ATL的目的就是使編寫組件對象和ActiveX控件更容易一些(ATL是在微軟開發新產品ActiveX-某某的過程中開發的,那些ActiveX-某某現在被稱為某某.NET)。由於ATL是為了便於編寫組件對象而存在的,所以只提供了簡單的界面類,相當於MFC的窗口類(CWnd)和對話框類(CDialog)。幸運的是這些類非常的靈活,能夠在其基礎上構建象WTL這樣的附加類。
WTL現在已經是第二次修正了,最初的版本是3.1,現在的版本是7(WTL的版本號之所以這樣選擇是為了與ATL的版本匹配,所以不存在1和2這樣的版本號)。WTL 3.1可以與VC 6和VC 7一起使用,但是在VC 7下需要定義幾個預處理標號。WTL 7向下兼容WTL 3.1,並且不作任何修改就可以與VC 7一起使用,現在看來沒有任何理由還使用3.1來進行新的開發工作。
ATL-style 模板
即使你能夠毫不費力地閱讀C++的模板類代碼,仍然有兩件事可能會使你有些頭暈,以下面這個類的定義為例:
class CMyWnd : public CWindowImpl<CMyWnd>
{
...
};
這樣作是合法的,因為C++的語法解釋說即使CMyWnd類只是被部分定義,類名CMyWnd已經被列入遞歸繼承列表,是可以使用的。將類名作為模板類的參數是因為ATL要做另一件詭秘的事情,那就是編譯期間的虛函數調用機制。
如果你想要了解它是如何工作地,請看下面的例子:
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) 就是竅門所在。它根據函數調用時的特殊處理將指向B1類型的指針this指派為D1或D2類型的指針,因為模板代碼是在編譯其間生成的,所以只要編譯器生成正確的繼承列表,這樣指派就是安全的。(如果你寫成:
class D3 : public B1<D2>
就會有麻煩) 之所以安全是因為this對象只可能是指向D1或D2(在某些情況下)類型的對象,不會是其他的東西。注意這很像C++的多態性(polymorphism),只是SayHi()方法不是虛函數。
要解釋這是如何工作的,首先看對每個SayHi()函數的調用,在第一個函數調用,對象B1被指派為D1,所以代碼被解釋成:
void B1<D1>::SayHi()
{
D1* pT = static_cast<D1*>(this);
pT->PrintClassName();
}
由於D1沒有重載PrintClassName(),所以查看基類B1,B1有PrintClassName(),所以B1的PrintClassName()被調用。
現在看第二個函數調用SayHi(),這一次對象被指派為D2類型,SayHi()被解釋成:
void B1<D2>::SayHi()
{
D2* pT = static_cast<D2*>(this);
pT->PrintClassName();
}
這一次,D2含有PrintClassName()方法,所以D2的PrintClassName()方法被調用。
這種技術的有利之處在於:
節省虛函數表在這個例子中看起來無足輕重(每個虛函數只有4個字節),但是設想一下如果有15個基類,每個類含有20個方法,加起來就相當可觀了。
ATL 窗口類好了,關於ATL的背景知識已經講的構多了,到了該正式講ATL的時候了。ATL在設計時接口定義和實現是嚴格區分開的,這在窗口類的設計中是最明顯的,這一點類似於COM,COM的接口定義和實現是完全分開的(或者可能有多個實現)。
ATL有一個專門為窗口設計的接口,可以做全部的窗口操作,這就是CWindow。它實際上就是對HWND操作的包裝類,對幾乎所有以HWND句柄為第一個參數的窗口API的進行了封裝,例如:SetWindowText() 和 DestroyWindow()。CWindow類有一個公有成員m_hWnd,使你可以直接對窗口的句柄操作,CWindow還有一個操作符HWND,你可以講CWindow對象傳遞給以HWND為參數的函數,但這與CWnd::GetSafeHwnd()(譯者加:MFC的方法)沒有任何等同之處。
CWindow 與 MFC 的CWnd類有很大的不同,創建一個CWindow對象占用很少的資源,因為只有一個數據成員,沒有MFC窗口中的對象鏈,MFC內部維持這一個對象鏈,此對象鏈將HWND映射到CWnd對象。還有一點與MFC的CWnd類不同的是當一個CWindow對象超出了作用域,它關聯的窗口並不被銷毀掉,這意味著你並不需要隨時記得分離你所創建的臨時CWindow對象。
在ATL類中對窗口過程的實現是CWindowImpl。CWindowImpl 含有所有窗口實現代碼,例如:窗口類的注冊,窗口的子類化,消息映射以及基本的WindowProc()函數,可以看出這與MFC的設計有很大的不同,MFC將所有的代碼都放在一個CWnd類中。
還有兩個獨立的類包含對話框的實現,它們分別是CDialogImpl 和 CAxDialogImpl,CDialogImpl 用於實現普通的對話框而CAxDialogImpl實現含有ActiveX控件的對話框。
定義一個窗口的實現任何非對話框窗口都是從CWindowImpl 派生的,你的新類需要包含三件事情:
窗口類的定義通過DECLARE_WND_CLASS宏或DECLARE_WND_CLASS_EX宏來實現。這輛個宏定義了一個CWndClassInfo結構,這個結構封裝了WNDCLASSEX結構。DECLARE_WND_CLASS宏讓你指定窗口類的類名,其他參數使用默認設置,而DECLARE_WND_CLASS_EX宏還允許你指定窗口類的類型和窗口的背景顏色,你也可以用NULL作為類名,ATL會自動為你生成一個類名。
讓我們開始定義一個新類,在後面的章節我會逐步的完成這個類的定義。
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
};
接下來是消息映射鏈,ATL的消息映射鏈比MFC的簡單的多,ATL的消息映射鏈被展開為switch語句,switch語句正確的消息處理者並調用相應的函數。使用消息映射鏈的宏是BEGIN_MSG_MAP 和 END_MSG_MAP,讓我們為我們的窗口添加一個空的消息映射鏈。
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};
我將在下一節展開講如何如何添加消息處理到消息映射鏈。最後,我們需要為我們的窗口類定義窗口的特征,窗口的特征就是窗口類型和擴展窗口類型的聯合體,用於創建窗口時指定窗口的類型。窗口類型被指定為參數模板,所以窗口的調用者不需要為指定窗口的正確類型而煩心,下面是是同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()
};
調用者可以重載CMyWindowTraits的類型定義,但是一般情況下這是沒有必要的,ATL提供了幾個預先定義的特殊的類型,其中之一就是CFrameWinTraits,一個非常棒的框架窗口:
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CFrameWinTraits;
填寫消息映射鏈
ATL的消息映射鏈是對開發者不太友好的部分,也是WTL對其改進最大的部分。類向導至少可以讓你添加消息響應,然而ATL沒有消息相關的宏和象MFC那樣的參數自動展開功能,在ATL中只有三種類型的消息處理,一個是WM_NOTIFY,一個是WM_COMMAND,第三類是其他窗口消息,讓我們開始為我們的窗口添加WM_CLOSE 和 WM_DESTROY的消息相應函數。
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值,你需要自己將其展開為相應的消息所需要的參數。還有第四個參數bHandled,這個參數在消息相應函數調用被ATL設置為TRUE,如果在你的消息響應處理完之後需要ATL調用默認的WindowProc()處理該消息,你可以講bHandled設置為FALSE。這與MFC不同,MFC是顯示的調用基類的響應函數來實現的默認的消息處理的。
讓我們也添加一個對WM_COMMAND消息的處理,假設我們的窗口有一個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宏已經將消息的參數展開了,同樣,NOTIFY_HANDLER宏也將WM_NOTIFY消息的參數展開了。
高級消息映射鏈和嵌入類ATL的另一個顯著不同之處就是任何一個C++類都可以響應消息,而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編程的頭文件,所以我們不需要在包含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菜單並顯示一個對話框,主要是為它增加一些情趣。
ATL中的對話框
我們前面提到過,ATL有兩個對話框類,我們的About對話框使用CDialogImpl。生成一個新對話框和生成一個主窗口幾乎一樣,只有兩點不同:
現在開始為About對話框定義一個新類:
class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
END_MSG_MAP()
};
ATL沒有在內部實現對“OK”和“Cancel”兩個按鈕的響應處理,所以我們需要自己添加這些代碼,如果用戶用鼠標點擊標題欄的關閉按鈕,WM_CLOSE的響應函數就會被調用。我們還需要處理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;
}
};
我使用一個消息響應函數同時處理“OK”和“Cancel”兩個按鈕的WM_COMMAND消息,因為命令響應函數的wID參數就已經指明了消息是來自“OK”按鈕還是來自“Cancel”按鈕。
顯示對話框的方法與MFC相似,創建一個新對話框類的實例,然後調用DoModal()方法。現在我們返回主窗口,添加一個帶有About菜單項的菜單用來顯示我們的對話框,這需要再添加兩個消息響應函數,一個是響應WM_CREATE
,另一個是響應菜單的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;
}
// ...
};
在指定對話框的父窗口的方式上有些不同,MFC是通過構造函數將父窗口的指針傳遞給對話框而在ATL中是將父窗口的指針作為DoModal()方法的第一個參數傳遞給對話框的,如果象上面的代碼一樣沒有指定父窗口,ATL會使用GetActiveWindow()
得到的窗口(也就是我們的主框架窗口)作為對話框的父窗口。
對LoadMenu()方法的調用展示了CComModule的另一個方法-GetResourceInstance(),它返回你的EXE的HINSTANCE實例,和MFC的AfxGetResourceHandle()方法相似。(當然還有CComModule::GetModuleInstance(),它相當於MFC的AfxGetInstanceHandle()。)
這就是主窗口和對話框的顯示效果:
我會繼續講WTL的,只是會在第二部分。我覺得既然這些文章是寫給使用MFC的開發者的,所以有必要在投入WTL之前先介紹一些ATL。如果你是第一次接觸到ATL,那現在你就可以嘗試寫一些小程序,處理消息和使用嵌入類,你也可以嘗試用類向導支持消息映射鏈,使它能夠自動添加消息響應。現在就開始,右鍵單擊CMyWindow項,在彈出的上下文菜單中單擊“Add Windows Message Handler”菜單項。
在第二部分,我將全面介紹基本的WTL窗口類和一個更好的消息映射宏。
修改記錄2003年3月22日,本文第一次發表。