1、模板、文檔、視圖、架構的關係
連載1~5我們各個擊破地講解了文檔、文件範本、視圖和架構類,連載1已經強調這些類有著親密的內部聯絡,總結1~5我們可以概括其聯絡為:
(1)文檔保留該文檔的視圖列表和指向建立該文檔的文件範本的指標;文檔至少有一個相關聯的視圖,而視圖只能與一個文檔相關聯。
(2)視圖保留指向其文檔的指標,並被包含在其父架構視窗中;
(3)文檔架構視窗(即包含視圖的MDI子視窗)保留指向其當前即時檢視的指標;
(4)文件範本保留其已開啟文檔的列表,維護架構視窗、文檔及視圖的映射;
(5)應用程式保留其文件範本的列表。
我們可以通過一組函數讓這些類之間相互可訪問,表6-1給出這些函數。
表6-1 文檔、文件範本、視圖和架構類的互相訪問
| 從該對象 |
如何訪問其他對象 |
| 全域函數 |
調用全域函數AfxGetApp可以得到CWinApp應用類指標 |
| 應用 |
AfxGetApp()->m_pMainWnd為架構視窗指標;用CWinApp::GetFirstDocTemplatePostion、CWinApp::GetNextDocTemplate來遍曆所有文件範本 |
| 文檔 |
調用CDocument::GetFirstViewPosition,CDocument::GetNextView來遍曆所有和文檔關聯的視圖;調用CDocument:: GetDocTemplate 擷取文件範本指標 |
| 文件範本 |
調用CDocTemplate::GetFirstDocPosition、CDocTemplate::GetNextDoc來遍曆所有對應文檔 |
| 視圖 |
調用CView::GetDocument 得到對應的文檔指標; 調用CView::GetParentFrame 擷取架構視窗 |
| 文檔架構視窗 |
調用CFrameWnd::GetActiveView 擷取當前得到當前即時檢視指標; 調用CFrameWnd::GetActiveDocument 擷取附加到當前視圖的文檔指標 |
| MDI 架構視窗 |
調用CMDIFrameWnd::MDIGetActive 擷取當前活動的MDI子視窗(CMDIChildWnd) |
我們列舉一個例子,綜合應用上表中的函數,寫一段代碼,它完成遍曆文件範本、文檔和視圖的功能:
CMyApp *pMyApp = (CMyApp*)AfxGetApp(); //得到應用程式指標 POSITION p = pMyApp->GetFirstDocTemplatePosition();//得到第1個文件範本 while (p != NULL) //遍曆文件範本 { CDocTemplate *pDocTemplate = pMyApp->GetNextDocTemplate(p); POSITION p1 = pDocTemplate->GetFirstDocPosition();//得到文件範本對應的第1個文檔 while (p1 != NULL) //遍曆文件範本對應的文檔 { CDocument *pDocument = pDocTemplate->GetNextDoc(p1); POSITION p2 = pDocument->GetFirstViewPosition(); //得到文檔對應的第1個視圖 while (p2 != NULL) //遍曆文檔對應的視圖 { CView *pView = pDocument->GetNextView(p2); } } } |
由此可見,下面的管理關聯性和實現途徑都是完全類似的:
(1)應用程式之於文件範本;
(2)文件範本之於文檔;
(3)文檔之於視圖。
圖6.1、6.2分別給出了一個多文檔/視圖架構MFC程式的組成以及其中所包含類的層次關係。
關於文檔和視圖的關係,我們可進一步細分為三類:
(1)文檔對應多個相同的視圖對象,每個視圖對象在一個單獨的 MDI 文檔架構視窗中;
(2)文檔對應多個相同類的視圖對象,但這些視圖對象在同一文檔架構視窗中(通過"拆分視窗"即將單個文件視窗的視圖空間拆分成多個單獨的文檔視圖實現);
(3)文檔對應多個不同類的視圖對象,這些視圖對象僅在一個單獨的 MDI 文檔架構視窗中。在此模型中,由不同的類構造成的多個視圖共用單個架構視窗,每個視圖可提供查看同一文檔的不同方式。例如,一個視圖以文書處理模式顯示文檔,而另一個視圖則以"文件引導模式"模式顯示文檔。
圖6.3顯示了對應三種文檔與視圖關係應用程式的介面特點。
2. 訊息流程動機制
在基於"文檔/視圖"架構的MFC程式中,使用者訊息(滑鼠、鍵盤輸入等)會先發往視圖,如果視圖未處理則會發往架構視窗。所以,一般來說,訊息映射宜定義在視圖中。另外,如果一個應用同時擁有多個視圖而當前即時檢視沒有對訊息進行處理則訊息也會發往架構視窗。
下面我們來看執行個體,我們利用Visual C++嚮導建立一個單文檔/視圖架構的MFC程式,在其中增加一個功能表項目為"自訂"(ID為IDM_SELF,6.4)。
圖6.4 含"自訂"菜單的單文檔/視圖架構MFC程式 |
我們分別在視圖類和架構視窗類別中為"自訂"菜單添加訊息映射,代碼如下:
//視圖中的訊息映射和處理函數 BEGIN_MESSAGE_MAP(CExampleView, CView) //{{AFX_MSG_MAP(CExampleView) ON_COMMAND(IDM_SELF, OnSelf) //}}AFX_MSG_MAP END_MESSAGE_MAP() void CExampleView::OnSelf() { // TODO: Add your command handler code here AfxMessageBox("訊息在視圖中處理"); }//架構視窗中的訊息映射和處理函數 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_COMMAND(IDM_SELF, OnSelf) //}}AFX_MSG_MAP END_MESSAGE_MAP() void CMainFrame::OnSelf() { // TODO: Add your command handler code here AfxMessageBox("訊息在架構視窗中處理"); } |
這時候,我們單擊"自訂"菜單,彈出對話方塊顯示"訊息在視圖中處理";如果我們刪除架構視窗中的訊息映射,再單擊"自訂"菜單,彈出對話方塊也顯示"訊息在視圖中處理";但是,若我們將視圖中的訊息映射刪除了,就會顯示"訊息在架構視窗中處理"!這驗證了我們關於訊息處理順序論述的正確性。
欲深入理解訊息流程動過程,還需認真分析CFrameWnd::OnCmdMsg、CView::OnCmdMsg函數的原始碼:
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // 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; } 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 BOOL bHandled = FALSE; if (m_pDocument != NULL) { // special state for saving view before routing to document _AFX_THREAD_STATE* pThreadState = AfxGetThreadState(); CView* pOldRoutingView = pThreadState->m_pRoutingView; pThreadState->m_pRoutingView = this; bHandled = m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); pThreadState->m_pRoutingView = pOldRoutingView; } return bHandled; } |
分析上述原始碼可知,WM_COMMAND訊息的實際流動順序比前文敘述的"先視圖,後架構視窗"要複雜得多,文檔和應用程式都參與了訊息的處理過程。如果我們再為文檔和應用添加訊息映射和處理函數:
//文檔的訊息映射和處理函數 BEGIN_MESSAGE_MAP(CExampleDoc, CDocument) //{{AFX_MSG_MAP(CExampleDoc) ON_COMMAND(IDM_SELF, OnSelf) //}}AFX_MSG_MAP END_MESSAGE_MAP()void CExampleDoc::OnSelf() { // TODO: Add your command handler code here AfxMessageBox("訊息在文檔中處理"); } //應用的訊息映射和處理函數 BEGIN_MESSAGE_MAP(CExampleApp, CWinApp) //{{AFX_MSG_MAP(CExampleApp) ON_COMMAND(IDM_SELF, OnSelf) //}}AFX_MSG_MAP END_MESSAGE_MAP() void CExampleApp::OnSelf() { // TODO: Add your command handler code here AfxMessageBox("訊息在應用中處理"); } |
屏蔽掉視圖和架構視窗的訊息映射,再單擊"自訂"菜單,彈出對話方塊顯示"訊息在文檔中處理";再屏蔽掉文檔中的訊息映射,彈出對話方塊顯示"訊息在應用中處理"!由此可見,完整的WM_COMMAND訊息的處理順序是"視圖――文檔――架構視窗――應用"!
實際上,關於MFC的訊息流程動是一個很複雜的議題,陷於篇幅的原因,我們不可能對其進行更詳盡的介紹,讀者可自行尋找相關資料。