通過分析MFC的源代碼,我們可以得到WM_COMMAND的消息響應順序如下:
多文檔框架中,有打開的文檔時:視圖 > 文檔 > 子框架窗口 > 應用程序 >主框架窗口
多文檔框架在沒有打開文檔時,應用程序和主框架窗口的順序相反:主框架窗口 > 應用程序
在單文檔框架應用程序中,因爲沒有子框架窗口,所以順序應該是:視圖 > 文檔 >主框架窗口> 應用程序。無論有沒有打開文檔,主框架窗口都比應用程序類更優先。
事實上在多文檔框架中,系統自動生成的代碼使用的是CDocTemplate的子類CMultiDocTemplate。在應用程序類(多文檔應用框架)的InitInstance()裏可以找到類似下面的代碼:
// 註冊應用程序的文檔模板。文檔模板 // 將用作文檔、框架窗口和視圖之間的連接 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_test3TYPE, RUNTIME_CLASS(Ctest3Doc), RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架 RUNTIME_CLASS(Ctest3View)); if (!pDocTemplate) return FALSE; 而CMultiDocTemplate類並沒有重寫OnCmdMsg函數,所以依然不影響WM_COMMAND消息的響應順序。 BOOL CDocTemplate::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) ...{ BOOL bReturn; CCmdTarget* pFactory = DYNAMIC_DOWNCAST(CCmdTarget, m_pAttachedFactory); if (nCode == CN_OLE_UNREGISTER && pFactory != NULL) bReturn = pFactory->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); else bReturn = CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); return bReturn; }
因爲應用程序中並沒有開發直接從CDocTemplate繼承的類,所以應用程序無法使用CDocTemplate的子類定義WM_COMMAND消息的響應函數。CDocTemplate類在OnCmdMsg函數實現裏調用的CCmdTarget::OnCmdMsg實現不了任何消息的傳遞。
視圖CView > 文檔CDocument > 子框架CFrame > 應用程序CWinApp >主框架窗口CMDIFrameWnd
再查看CDocument的OnCmdMsg的實現代碼:
文檔類CDocument首先查找自己的響應函數(調用CCmdTarget::OnCmdMsg),然後讓其成員m_pDocTemplate來處理。m_pDocTemplate是文檔模板類CDocTemplate的指針,MFC框架使用文檔模板來實現自動化的文檔管理。在應用程序開發過程中,沒有針對文檔模板類的編程。查看CDocTemplate類中OnCmdMsg的實現:
BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) ...{ if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise check template if (m_pDocTemplate != NULL && m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; }
存在打開的視圖時,視圖CView > 子框架CFrame > 應用程序CWinApp >主框架窗口CMDIFrameWnd
沒有打開視圖時,主框架窗口CMDIFrameWnd > 應用程序CWinApp
再看視圖CView類對OnCmdMsg的處理:
BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) ...{ // first pump through pane if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // then pump through document if (m_pDocument != NULL) ...{ // special state for saving view before routing to document CPushRoutingView push(this); return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } return FALSE; }
視圖類首先查找自己的消息響應表(調用CWnd::OnCmdMsg),然後讓視圖對應的CDocument類處理消息。所以可以得出如下順:
子框架窗口CMDIChildWnd > 主框架窗口CMDIFrameWnd
CMDIChildWnd窗口沒有重載OnCmdMsg函數,所以使用基本框架窗口CFrameWnd的OnCmdMsg函數: BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) ...{ CPushRoutingFrame push(this); // pump through current view FIRST CView* pView = GetActiveView(); if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // then pump through frame if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // last but not least, pump through app CWinApp* pApp = AfxGetApp(); if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; }
基本框架窗口先讓當前活動視圖CView響應函數,然後再查找自己的消息響應表(調用CWnd::OnCmdMsg),最後讓應用程序處理該消息。所以得出順序:
MFC:
Win32的封裝,窗口體系單元化,單元子父類繪製關係體系和消息響應機制,分別是互繪製機制和
消息傳遞機制
在MFC開發的程序中,菜單、工具條按鈕等都會產生WM_COMMAND消息。而在MFC的Document/View框架中,有很多類可以響應WM_COMMAND消息,分別是框架類:CFrameWnd、CMDIChildWnd、CMDIFrameWnd;應用程序類CWinApp;文檔類CDocument;以及視圖類CView。
當應用程序主菜單發送了一個WM_COMMAND消息時,WM_COMMAND消息將會按一定順序被交這些類的實例,並調用第一個發現的響應函數。以多文檔視圖框架應用程序爲例,我們可以分析MFC中這些類的源代碼,並一步一步找出WM_COMMAND消息的響應順序。
因爲框架窗口是菜單的父窗口,所以消息首先發到CMDIFrameWnd類的實例(也就是主框架窗口,通常爲CMainFrame)。CMDIFrameWnd類的OnCmdMsg函數如下:
BOOL CMDIFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) ...{ CMDIChildWnd* pActiveChild = MDIGetActive(); // pump through active child FIRST if (pActiveChild != NULL) ...{ CPushRoutingFrame push(this); if (pActiveChild->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; } // then pump through normal frame return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); }
主框架窗口CMDIFrameWnd先查找當前活動的子框架窗口CMDIChildWnd,讓其優先訪問,然後調用基本框架窗口CFrameWnd的響應函數。所以響應優先級: