cbNotes的專欄:不積矽步,無以至千里;不積小流,無以成江海!
阿新 • • 發佈:2019-01-07
CDocTemplate類的AddDocument、RemoveDocument成員函式使得CDocument* pDoc引數所指向的文件歸屬於本文件模板(通過將this指標賦值給pDoc所指向CDocument物件的m_pDocTemplate成員變數)或脫離與本文件模板的關係:
而CDocTemplate類的CreateNewDocument成員函式則首先呼叫CDocument執行時類的CreateObject函式建立一個CDocument物件,再呼叫AddDocument成員函式將其歸屬於本文件模板類:
文件類物件由文件模板類構造生成,單文件模板類CSingleDocTemplate只能生成一個文件類物件,並用成員變數 m_pOnlyDoc 指向該物件;多文件模板類可以生成多個文件類物件,用成員變數 m_docList 指向文件物件組成的連結串列。
CSingleDocTemplate的建構函式、AddDocument及RemoveDocument成員函式都在CDocTemplate類相應函式的基礎上增加了對m_pOnlyDoc指標的處理:
同樣,CMultiDocTemplate類的相關函式也需要對m_docList所指向的連結串列進行操作(實際上AddDocument和RemoveDocument成員函式是文件模板管理其所包含文件的函式):
由於CMultiDocTemplate類可包含多個文件,依靠其成員函式GetFirstDocPosition和GetNextDoc完成對文件連結串列m_docList的遍歷:
而CSingleDocTemplate的這兩個函式實際上並無太大的意義,僅僅是MFC要玩的某種"招數",這個"招數"高明嗎?相信看完MFC的相關原始碼後你或許不會這麼認為,實際上CSingleDocTemplate的GetFirstDocPosition、GetNextDoc函式僅僅只能判斷m_pOnlyDoc的是否為NULL:
筆者認為,MFC的設計者們將GetFirstDocPosition、GetNextDoc作為基類CDocTemplate的成員函式是不合理的,一種更好的做法是將GetFirstDocPosition、GetNextDoc移至CMultiDocTemplate派生類。
CDocTemplate還需完成對其對應文件的關閉與儲存操作:
前文我們提到,由於MFC的設計者將CSingleDocTemplate和CMultiDocTemplate的行為未進行規範的區分,它對僅僅對應一個文件的CSingleDocTemplate也提供了所謂的GetFirstDocPosition、GetNextDoc遍歷操作,所以基類CDocTemplate的SaveAllModified和CloseAllDocuments函式(都是遍歷)就可統一CSingleDocTemplate和CMultiDocTemplate兩個本身並不相同類的SaveAllModified和CloseAllDocuments行為(實際上,對於CSingleDocTemplate而言,SaveAllModified和CloseAllDocuments中的"All"是沒有太大意義的。教室裡有1個老師和N個同學,老師可以對同學們說"所有同學",而學生對老師說"所有老師"相信會被當成神經病)。MFC的設計者們特意使用了"將錯就錯"的方法意圖簡化CSingleDocTemplate和CMultiDocTemplate類的設計,讀者朋友可以不認同他們的做法。
CDocTemplate還提供了框架視窗的建立和初始化函式:
3. CWinApp與CDocManager/CDocTemplate類
應用程式CWinApp類物件與CDocManager和CDocTemplate類的關係是:CWinApp物件中包含一個CDocManager指標型別的共有資料成員m_pDocManager,CWinApp::InitInstance函式呼叫CWinApp::AddDocTemplate函式向連結串列m_templateList新增模板指標(實際上是呼叫前文所述CDocManager的AddDocTemplate函式)。另外,CWinApp也提供了GetFirstDocTemplatePosition和GetNextDocTemplate函式實現來對m_templateList連結串列進行訪問(實際上也是呼叫了前文所述CDocManager的GetFirstDocTemplatePosition、GetNextDocTemplate函式)。我們僅摘取CWinApp類宣告的一小部分:
來看CWinApp派生類CSDIExampleApp(單文件)、CMDIExampleApp(多文件)的InitInstance成員函式的例子(僅僅摘取與文件模板相關的部分):
讀者朋友,看完本次連載,也許您有許多不明白的地方,這是正常的。因為其所講解的內容與後續幾次連載息息相關,我們愈往後看,就會愈加清晰。對於本次連載的內容,您只需要建立基本的印象。最初的淺嘗輒止是為了最終的深入脊髓!
我們試圖對MFC的深層機理刨根究底,"撥開雲霧見月明"的過程是艱辛的!
void CDocTemplate::AddDocument(CDocument* pDoc) { ASSERT_VALID(pDoc); ASSERT(pDoc->m_pDocTemplate == NULL); // no template attached yet pDoc->m_pDocTemplate = this; } void CDocTemplate::RemoveDocument(CDocument* pDoc) { ASSERT_VALID(pDoc); ASSERT(pDoc->m_pDocTemplate == this); // must be attached to us pDoc->m_pDocTemplate = NULL; } |
而CDocTemplate類的CreateNewDocument成員函式則首先呼叫CDocument執行時類的CreateObject函式建立一個CDocument物件,再呼叫AddDocument成員函式將其歸屬於本文件模板類:
CDocument* CDocTemplate::CreateNewDocument() { // default implementation constructs one from CRuntimeClass if (m_pDocClass == NULL) { TRACE0("Error: you must override CDocTemplate::CreateNewDocument.\n"); ASSERT(FALSE); return NULL; } CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject(); if (pDocument == NULL) { TRACE1("Warning: Dynamic create of document type %hs failed.\n",m_pDocClass->m_lpszClassName); return NULL; } ASSERT_KINDOF(CDocument, pDocument); AddDocument(pDocument); return pDocument; } |
文件類物件由文件模板類構造生成,單文件模板類CSingleDocTemplate只能生成一個文件類物件,並用成員變數 m_pOnlyDoc 指向該物件;多文件模板類可以生成多個文件類物件,用成員變數 m_docList 指向文件物件組成的連結串列。
CSingleDocTemplate的建構函式、AddDocument及RemoveDocument成員函式都在CDocTemplate類相應函式的基礎上增加了對m_pOnlyDoc指標的處理:
CSingleDocTemplate::CSingleDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass) : CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass) { m_pOnlyDoc = NULL; } void CSingleDocTemplate::AddDocument(CDocument* pDoc) { ASSERT(m_pOnlyDoc == NULL); // one at a time please ASSERT_VALID(pDoc); CDocTemplate::AddDocument(pDoc); m_pOnlyDoc = pDoc; } void CSingleDocTemplate::RemoveDocument(CDocument* pDoc) { ASSERT(m_pOnlyDoc == pDoc); // must be this one ASSERT_VALID(pDoc); CDocTemplate::RemoveDocument(pDoc); m_pOnlyDoc = NULL; } |
同樣,CMultiDocTemplate類的相關函式也需要對m_docList所指向的連結串列進行操作(實際上AddDocument和RemoveDocument成員函式是文件模板管理其所包含文件的函式):
// CMultiDocTemplate document management (a list of currently open documents) void CMultiDocTemplate::AddDocument(CDocument* pDoc) { ASSERT_VALID(pDoc); CDocTemplate::AddDocument(pDoc); ASSERT(m_docList.Find(pDoc, NULL) == NULL); // must not be in list m_docList.AddTail(pDoc); } void CMultiDocTemplate::RemoveDocument(CDocument* pDoc) { ASSERT_VALID(pDoc); CDocTemplate::RemoveDocument(pDoc); m_docList.RemoveAt(m_docList.Find(pDoc)); } |
由於CMultiDocTemplate類可包含多個文件,依靠其成員函式GetFirstDocPosition和GetNextDoc完成對文件連結串列m_docList的遍歷:
POSITION CMultiDocTemplate::GetFirstDocPosition() const { return m_docList.GetHeadPosition(); } CDocument* CMultiDocTemplate::GetNextDoc(POSITION& rPos) const { return (CDocument*)m_docList.GetNext(rPos); } |
而CSingleDocTemplate的這兩個函式實際上並無太大的意義,僅僅是MFC要玩的某種"招數",這個"招數"高明嗎?相信看完MFC的相關原始碼後你或許不會這麼認為,實際上CSingleDocTemplate的GetFirstDocPosition、GetNextDoc函式僅僅只能判斷m_pOnlyDoc的是否為NULL:
POSITION CSingleDocTemplate::GetFirstDocPosition() const { return (m_pOnlyDoc == NULL) ? NULL : BEFORE_START_POSITION; } CDocument* CSingleDocTemplate::GetNextDoc(POSITION& rPos) const { CDocument* pDoc = NULL; if (rPos == BEFORE_START_POSITION) { // first time through, return a real document ASSERT(m_pOnlyDoc != NULL); pDoc = m_pOnlyDoc; } rPos = NULL; // no more return pDoc; } |
筆者認為,MFC的設計者們將GetFirstDocPosition、GetNextDoc作為基類CDocTemplate的成員函式是不合理的,一種更好的做法是將GetFirstDocPosition、GetNextDoc移至CMultiDocTemplate派生類。
CDocTemplate還需完成對其對應文件的關閉與儲存操作:
BOOL CDocTemplate::SaveAllModified() { POSITION pos = GetFirstDocPosition(); while (pos != NULL) { CDocument* pDoc = GetNextDoc(pos); if (!pDoc->SaveModified()) return FALSE; } return TRUE; } void CDocTemplate::CloseAllDocuments(BOOL) { POSITION pos = GetFirstDocPosition(); while (pos != NULL) { CDocument* pDoc = GetNextDoc(pos); pDoc->OnCloseDocument(); } } |
CDocTemplate還提供了框架視窗的建立和初始化函式:
///////////////////////////////////////////////////////////////////////////// // Default frame creation CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther) { if (pDoc != NULL) ASSERT_VALID(pDoc); // create a frame wired to the specified document ASSERT(m_nIDResource != 0); // must have a resource ID to load from CCreateContext context; context.m_pCurrentFrame = pOther; context.m_pCurrentDoc = pDoc; context.m_pNewViewClass = m_pViewClass; context.m_pNewDocTemplate = this; if (m_pFrameClass == NULL) { TRACE0("Error: you must override CDocTemplate::CreateNewFrame.\n"); ASSERT(FALSE); return NULL; } CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject(); if (pFrame == NULL) { TRACE1("Warning: Dynamic create of frame %hs failed.\n",m_pFrameClass->m_lpszClassName); return NULL; } ASSERT_KINDOF(CFrameWnd, pFrame); if (context.m_pNewViewClass == NULL) TRACE0("Warning: creating frame with no default view.\n"); // create new from resource if (!pFrame->LoadFrame(m_nIDResource,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles NULL, &context)) { TRACE0("Warning: CDocTemplate couldn't create a frame.\n"); // frame will be deleted in PostNcDestroy cleanup return NULL; } // it worked ! return pFrame; } void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,BOOL bMakeVisible) { // just delagate to implementation in CFrameWnd pFrame->InitialUpdateFrame(pDoc, bMakeVisible); } |
3. CWinApp與CDocManager/CDocTemplate類
應用程式CWinApp類物件與CDocManager和CDocTemplate類的關係是:CWinApp物件中包含一個CDocManager指標型別的共有資料成員m_pDocManager,CWinApp::InitInstance函式呼叫CWinApp::AddDocTemplate函式向連結串列m_templateList新增模板指標(實際上是呼叫前文所述CDocManager的AddDocTemplate函式)。另外,CWinApp也提供了GetFirstDocTemplatePosition和GetNextDocTemplate函式實現來對m_templateList連結串列進行訪問(實際上也是呼叫了前文所述CDocManager的GetFirstDocTemplatePosition、GetNextDocTemplate函式)。我們僅摘取CWinApp類宣告的一小部分:
class CWinApp : public CWinThread { … CDocManager* m_pDocManager; // Running Operations - to be done on a running application // Dealing with document templates void AddDocTemplate(CDocTemplate* pTemplate); POSITION GetFirstDocTemplatePosition() const; CDocTemplate* GetNextDocTemplate(POSITION& pos) const; // Dealing with files virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named file void CloseAllDocuments(BOOL bEndSession); // close documents before exiting // Command Handlers protected: // map to the following for file new/open afx_msg void OnFileNew(); afx_msg void OnFileOpen(); int GetOpenDocumentCount(); … }; |
來看CWinApp派生類CSDIExampleApp(單文件)、CMDIExampleApp(多文件)的InitInstance成員函式的例子(僅僅摘取與文件模板相關的部分):
BOOL CSDIExampleApp::InitInstance() { … CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CSDIExampleDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CSDIExampleView)); AddDocTemplate(pDocTemplate); … return TRUE; } BOOL CMDIExampleApp::InitInstance() { … CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_MDIEXATYPE, RUNTIME_CLASS(CMDIExampleDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CMDIExampleView)); AddDocTemplate(pDocTemplate); … } |
讀者朋友,看完本次連載,也許您有許多不明白的地方,這是正常的。因為其所講解的內容與後續幾次連載息息相關,我們愈往後看,就會愈加清晰。對於本次連載的內容,您只需要建立基本的印象。最初的淺嘗輒止是為了最終的深入脊髓!
我們試圖對MFC的深層機理刨根究底,"撥開雲霧見月明"的過程是艱辛的!