首先看一下CMessageLoop的核心邏輯CMessageLoop.Run()的代碼:
int CMessageLoop.Run() { BOOL bDoIdle = TRUE; int nIdleCount = 0; BOOL bRet; for(;;) { while(!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE) && bDoIdle) { if(!OnIdle(nIdleCount++)) bDoIdle = FALSE; } bRet = ::GetMessage(&m_msg, NULL, 0, 0); if(bRet == -1) { ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)/n")); continue; // error, don't process } else if(!bRet) { ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting/n")); break; // WM_QUIT, exit message loop } if(!PreTranslateMessage(&m_msg)) { ::TranslateMessage(&m_msg); ::DispatchMessage(&m_msg); } if(IsIdleMessage(&m_msg)) { bDoIdle = TRUE; nIdleCount = 0; } } return (int)m_msg.wParam; } |
在上面的代碼中,有三個需要注意的地方。
消息循環中,首先調用PeekMessage()判斷消息隊列中是否有消息。如果沒有,則調用OnIdle()函數。這就是調用空閒處理。
第二個注意點是,如果有消息,則調用GetMessage()得到消息。然後做判斷,如果是錯誤返回,則對消息並不進行處理。然後再判斷是否是WM_QUIT消息,如果是,則退出消息循環,從而結束該界面線程。
接下來是第三個注意點。在TranslateMessage()消息之前,調用了成員函數PreTranslateMessage()。這爲在TranslateMessage()之前對消息進行處理提供了機會。
PreTranslateMessage()會遍歷CMessageLoop中所有CMessageFilterd對象的PreTranslateMessage()函數,直到其中一個返回爲TRUE或它們都返回爲FALSE。當有一個返回爲TRUE時,即對消息處理了,那麼,就不再調用TranslateMessage(),而是進入下一個循環。
這種消息過濾機制提供了一種在不同窗口之間傳遞消息的機制。
CMessageFilter是一個C++的接口,即只定義了抽象虛擬函數。
class CMessageFilter {public: virtual BOOL PreTranslateMessage(MSG* pMsg) = 0;}; |
這樣,任何類想要實現消息過濾,只需實現這個接口。在C++中就採用繼承。然後再實現PreTranslateMessage()函數即可。
ATL/WTL App Wizard生成的框架窗口中實現PreTranslateMessage()的代碼如下:
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { if(CFrameWindowImpl<CMainFrame> ::PreTranslateMessage(pMsg)) return TRUE; return m_view.PreTranslateMessage(pMsg);} |
這種消息過濾機制的好處是任何實現了CMessageFilter接口的對象,都可以接受消息過濾。
程序通過AddMessageFilter()和RemoveMessageFilter()把這些對象加入到CMessageLoop中。
空閒處理的機制和消息過濾類似。這裏不再介紹。我們要把主要經歷放在WTL的框架窗口分析上。稍後,我們將進入這部分內容。