在WTL裡面,CMainFrame的訊息迴圈是這樣的:
程式碼
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_Create, OnCreate)
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
而View裡面的訊息迴圈是這樣的:
程式碼
BEGIN_MSG_MAP(CGameView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_Create, OnCreate)
COMMAND_ID_HANDLER(ID_GAME_START_NEW, OnGameStartNew)
END_MSG_MAP()
雖然我明明寫了一個OnGameStartNew來響應功能表項目ID_GAME_START_NEW,但是通過實驗可以確定,CMainFrame並沒有將這個訊息轉交過來,而是自己截獲認為是無用的訊息扔掉了。那麼,如何讓CMainFrame將訊息交給View就是我要解決的問題了。
首先我想到的是在PreTranslateMessage這個函數上做文章,因為CMainFrame的PreTranslateMessage確實調用了 View的PreTranslateMessage函數,所以要想截獲這個訊息還是蠻容易的,但是由於自己要寫一個顯式的訊息迴圈,寫出來的代碼極為醜 陋,不爽,只把它作為最後保底的方案。
接著我想到的是向訊息迴圈註冊Message Filter的方法,也就是在View的OnCreate中加入以下代碼:
程式碼
// register object for message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);
並且,要讓View派生自CMessageFilter,如下所示:
程式碼
class CGameView
: public CScrollWindowImpl<CGameView>
, public CMessageFilter
{
// ...
};
但是很可惜,只有FrameWindow才能享用Message Filter帶來的靈活性,View不可以。
再接著,我嘗試使用CHAIN_MSG_MAP_MEMBER(),直接在CMainFrame的訊息迴圈裡面把View的訊息迴圈給插進來:
程式碼
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_Create, OnCreate)
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
CHAIN_MSG_MAP_MEMBER(m_view)
END_MSG_MAP()
不過讓我十分沮喪的是,這樣可以捕捉菜單訊息了,也非常的簡潔明了,但是View截獲了很多本來不應該收到的訊息,使得整個CMainFrame的顯示等等都亂了!這可不行!
最後,我找到了CHAIN_MSG_MAP_ALT_MEMBER()和ALT_MSG_MAP(),用它們一起配合,終於讓事情變得簡潔好用了!
先要改View裡面的訊息迴圈,把它寫成如下形式:
程式碼
BEGIN_MSG_MAP(CGameView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_Create, OnCreate)
ALT_MSG_MAP(1) // Handle notify message.
COMMAND_ID_HANDLER(ID_GAME_START_NEW, OnGameStartNew)
END_MSG_MAP()
然後在CMainFrame的訊息迴圈中加入CHAIN_MSG_MAP_ALT_MEMBER():
程式碼
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_Create, OnCreate)
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1)
END_MSG_MAP()
好了,這就可以了。
最後的最後,來廢話一下原理:
在ATL/WTL 裡面,為了能夠將好幾個訊息迴圈有選擇串起來,特別做了這個msgMapID的東西,訊息迴圈都是先switch (msgMapID),然後在不同的 Map ID中再來尋找對應的訊息響應函數。預設的Windows訊息始終使用Map ID為0的訊息響應函數,而一旦有需要,都可以手動改成指定的 Map ID。注意,不同的Map ID的訊息響應函數是互斥的,一次一般只可能使用一個Map(當然,非要手動把多個Map ID加入同一個訊息迴圈的 話也可以……)。
具體的實現代碼可以看WTL的源碼,如果有一定的Windows編程基礎的話,應該非常好懂。
使用Map ID的額外好處:
通過這種機制,我們可以很方便的在CMainFrame中將訊息派發到不同的控制項當中去,這樣就比MFC的訊息機制要靈活很多。