深入剖析WTL—WTL框架窗口分析(1)

WTL的基礎是ATL。WTL的框架窗口是ATL窗口類的繼承。因此,先介紹一下ATL對Windows窗口的封裝。

由第一部分介紹的Windows應用程序可以知道創建窗口和窗口工作的邏輯是:

1 註冊一個窗口類

2 創建該類窗口

3 顯示和激活該窗口

4 窗口的消息處理邏輯在窗口函數中。該函數在註冊窗口類時指定。

從上面的邏輯可以看出,要封裝窗口主要需解決怎樣封裝窗口消息處理機制。

對於窗口消息處理機制的封裝存在兩個問題。

一是,爲了使封裝好的類的窗口函數對外是透明的,我們就會想到,要將窗口函數的消息轉發到不同的類的實例。那麼怎樣將窗口函數中的消息轉發給封裝好的類的實例?因爲所有封裝好的類窗口的窗口函數只有一個,即一類窗口只有一個窗口函數。而我們希望的是將消息發送給某個類的實例。問題是窗口函數並不知道是哪個實例。它僅僅知道的是HWND,而不是類的實例的句柄。因此,必須有一種辦法,能通過該HWND,找到與之相對應的類的實例。

二是,假設已經解決了上面的問題。那麼怎樣將消息傳遞給相應的類的實例。通常的辦法是採用虛函數。將每個消息對應生成一個虛函數。這樣,在窗口處理函數中,對於每個消息,都調用其對應的虛函數即可。

但這樣,會有很多虛函數,使得類的虛函數表十分巨大。

爲此,封裝窗口就是要解決上面兩個基本問題。對於第二個問題,ATL是通過只定義一個虛函數。然後,通過使用宏,來生成消息處理函數。對於第一個問題,ATL通過使用動態改變HWND參數方法來實現的。

ATL對窗口的封裝


圖示是ATL封裝的類的繼承關係圖。從圖中可以看到有兩個最基本的類。一個是CWindow,另一個是CMessageMap。



CWindows是對Windows的窗口API的一個封裝。它把一個Windows句柄封裝了起來,並提供了對該句柄所代表的窗口的操作的API的封裝。

CWindow的實例是C++語言中的一個對象。它與實際的Windows的窗口通過窗口句柄聯繫。創建一個CWindow的實例時並沒有創建相應的Windows的窗口,必須調用CWindow.Create()來創建Windows窗口。也可以創建一個CWindow的實例,然後將它與已經存在的Windows窗口掛接起來。

CMessageMap僅僅定義了一個抽象虛函數——ProcessWindowMessage()。所有的包含消息處理機制的窗口都必須實現該函數。

通常使用ATL開發程序,都是從CWindowImplT類派生出來的。從類的繼承圖可以看出,該類具有一般窗口的操作功能和消息處理機制。

在開發應用程序的時候,你必須在你的類中定義“消息映射”。

BEGIN_MSG_MAP(CMainFrame)	MESSAGE_HANDLER(WM_CREATE, OnCreate)	COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)	COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)	COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)	COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)	COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)     CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)     CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)END_MSG_MAP()


我們知道一個窗口函數的通常結構就是許多的case語句。ATL通過使用宏,直接形成窗口函數的代碼。

消息映射是用宏來實現的。通過定義消息映射和實現消息映射中的消息處理句柄,編譯器在編譯時,會爲你生成你的窗口類的ProcessWindowMessage()。

消息映射宏包含消息處理宏和消息映射控制宏。

BEGIN_MSG_MAP()和END_MSG_MAP()


每個消息映射都由BEGIN_MSG_MAP()宏開始。我們看一下這個宏的定義:

#define BEGIN_MSG_MAP(theClass) /public: /	BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam,	LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) /	{ /		BOOL bHandled = TRUE; /		hWnd; /		uMsg; /		wParam; /		lParam; /		lResult; /		bHandled; /		switch(dwMsgMapID) /		{ /		case 0:


一目瞭然,這是函數ProcessWindowMessage()的實現。與MFC的消息映射相比,ATL的是多麼的簡單。記住越是簡單越吸引人。

需要注意的是dwMsgMapID。每個消息映射都有一個ID。後面會介紹爲什麼要用這個。

於是可以推斷,消息處理宏應該是該函數的函數體中的某一部分。也可以斷定END_MSG_MAP()應該定義該函數的函數結尾。

我們來驗證一下:

#define END_MSG_MAP() /		break; /	default: /		ATLTRACE2(atlTraceWindowing, 0, _T("Invalid message map ID (%i)/n"), dwMsgMapID); /		ATLASSERT(FALSE); /		break; /		} /		return FALSE; /	}

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章