MFC框架中WM_COMMAND訊息響應順序

Slin000發表於2008-01-11
 
在MFC開發的程式中,選單、工具條按鈕等都會產生WM_COMMAND訊息。而在MFC的Document/View框架中,有很多類可以響應WM_COMMAND訊息,分別是框架類:CFrameWnd、CMDIChildWndCMDIFrameWnd;應用程式類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的響應函式。所以響應優先順序:
子框架視窗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),最後讓應用程式處理該訊息。所以得出順序:
存在開啟的檢視時,檢視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類處理訊息。所以可以得出如下順:
檢視CView > 文件CDocument > 子框架CFrame > 應用程式CWinApp >主框架視窗CMDIFrameWnd
 
再檢視CDocument的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;
}

文件類CDocument首先查詢自己的響應函式(呼叫CCmdTarget::OnCmdMsg),然後讓其成員m_pDocTemplate來處理。m_pDocTemplate是文件模板類CDocTemplate的指標,MFC框架使用文件模板來實現自動化的文件管理。在應用程式開發過程中,沒有針對文件模板類的程式設計。檢視CDocTemplate類中OnCmdMsg的實現:
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實現不了任何訊息的傳遞。
事實上在多文件框架中,系統自動生成的程式碼使用的是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訊息的響應順序。
 
通過分析MFC的原始碼,我們可以得到WM_COMMAND的訊息響應順序如下:
多文件框架中,有開啟的文件時:檢視 > 文件 > 子框架視窗 > 應用程式 >主框架視窗
多文件框架在沒有開啟文件時,應用程式和主框架視窗的順序相反:主框架視窗 > 應用程式
 
在單文件框架應用程式中,因為沒有子框架視窗,所以順序應該是:檢視 > 文件 >主框架視窗> 應用程式。無論有沒有開啟文件,主框架視窗都比應用程式類更優先。
作者:蘇林
 

相關文章