深入浅出MFC文档/视图架构之文档模板
在"文檔/視圖"架構(gòu)的MFC程序中,提供了文檔模板管理者類CDocManager,由它管理應(yīng)用程序所包含的文檔模板。我們先看看這個類的聲明:
/ // CDocTemplate manager object class CDocManager : public CObject {DECLARE_DYNAMIC(CDocManager)public:// ConstructorCDocManager();//Document functionsvirtual void AddDocTemplate(CDocTemplate* pTemplate);virtual POSITION GetFirstDocTemplatePosition() const;virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const;virtual void RegisterShellFileTypes(BOOL bCompat);void UnregisterShellFileTypes();virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named filevirtual BOOL SaveAllModified(); // save before exitvirtual void CloseAllDocuments(BOOL bEndSession); // close documents before exitingvirtual int GetOpenDocumentCount();// helper for standard commdlg dialogsvirtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle,DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);//Commands// Advanced: process async DDE requestvirtual BOOL OnDDECommand(LPTSTR lpszCommand);virtual void OnFileNew();virtual void OnFileOpen();// Implementationprotected:CPtrList m_templateList;int GetDocumentCount(); // helper to count number of total documentspublic:static CPtrList* pStaticList; // for static CDocTemplate objectsstatic BOOL bStaticInit; // TRUE during static initializationstatic CDocManager* pStaticDocManager; // for static CDocTemplate objectspublic:virtual ~CDocManager();#ifdef _DEBUGvirtual void AssertValid() const;virtual void Dump(CDumpContext& dc) const;#endif };從上述代碼可以看出,CDocManager類維護(hù)一個CPtrList類型的鏈表m_templateList(即文檔模板鏈表,實際上,MFC的設(shè)計者在MFC的實現(xiàn)中大量使用了鏈表這種數(shù)據(jù)結(jié)構(gòu)),CPtrList類型定義為:
class CPtrList : public CObject {DECLARE_DYNAMIC(CPtrList)protected:struct CNode{CNode* pNext; CNode* pPrev;void* data;};public:// ConstructionCPtrList(int nBlockSize = 10);// Attributes (head and tail)// count of elementsint GetCount() const;BOOL IsEmpty() const;// peek at head or tailvoid*& GetHead();void* GetHead() const;void*& GetTail();void* GetTail() const;// Operations// get head or tail (and remove it) - don't call on empty list!void* RemoveHead();void* RemoveTail();// add before head or after tailPOSITION AddHead(void* newElement);POSITION AddTail(void* newElement);// add another list of elements before head or after tailvoid AddHead(CPtrList* pNewList);void AddTail(CPtrList* pNewList);// remove all elementsvoid RemoveAll();// iterationPOSITION GetHeadPosition() const;POSITION GetTailPosition() const;void*& GetNext(POSITION& rPosition); // return *Position++void* GetNext(POSITION& rPosition) const; // return *Position++void*& GetPrev(POSITION& rPosition); // return *Position--void* GetPrev(POSITION& rPosition) const; // return *Position--// getting/modifying an element at a given positionvoid*& GetAt(POSITION position);void* GetAt(POSITION position) const;void SetAt(POSITION pos, void* newElement);void RemoveAt(POSITION position);// inserting before or after a given positionPOSITION InsertBefore(POSITION position, void* newElement);POSITION InsertAfter(POSITION position, void* newElement);// helper functions (note: O(n) speed)POSITION Find(void* searchValue, POSITION startAfter = NULL) const;// defaults to starting at the HEAD// return NULL if not foundPOSITION FindIndex(int nIndex) const;// get the 'nIndex'th element (may return NULL)// Implementationprotected:CNode* m_pNodeHead;CNode* m_pNodeTail;int m_nCount;CNode* m_pNodeFree;struct CPlex* m_pBlocks;int m_nBlockSize;CNode* NewNode(CNode*, CNode*);void FreeNode(CNode*);public:~CPtrList();#ifdef _DEBUGvoid Dump(CDumpContext&) const;void AssertValid() const;#endif// local typedefs for class templatestypedef void* BASE_TYPE;typedef void* BASE_ARG_TYPE; };很顯然,CPtrList是對鏈表結(jié)構(gòu)體struct CNode
{
CNode* pNext;?
CNode* pPrev;
void* data;
};
本身及其GetNext、GetPrev、GetAt、SetAt、RemoveAt、InsertBefore、InsertAfter、Find、FindIndex等各種操作的封裝。
作為一個抽象的鏈表類型,CPtrList并未定義其中節(jié)點的具體類型,而以一個void指針(struct CNode 中的void* data)巧妙地實現(xiàn)了鏈表節(jié)點成員具體類型的"模板"化。很顯然,在Visual C++6.0開發(fā)的年代,C++語言所具有的語法特征"模板"仍然沒有得到廣泛的應(yīng)用。
而CDocManager類的成員函數(shù)
virtual void AddDocTemplate(CDocTemplate* pTemplate);
virtual POSITION GetFirstDocTemplatePosition() const;
virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const;
則完成對m_TemplateList鏈表的添加及遍歷操作的封裝,我們來看看這三個函數(shù)的源代碼:
void CDocManager::AddDocTemplate(CDocTemplate* pTemplate) {if (pTemplate == NULL){if (pStaticList != NULL){POSITION pos = pStaticList->GetHeadPosition();while (pos != NULL){CDocTemplate* pTemplate = (CDocTemplate*)pStaticList->GetNext(pos);AddDocTemplate(pTemplate);}delete pStaticList;pStaticList = NULL;}bStaticInit = FALSE;}else{ASSERT_VALID(pTemplate);ASSERT(m_templateList.Find(pTemplate, NULL) == NULL);// must not be in listpTemplate->LoadTemplate();m_templateList.AddTail(pTemplate);} } POSITION CDocManager::GetFirstDocTemplatePosition() const {return m_templateList.GetHeadPosition(); } CDocTemplate* CDocManager::GetNextDocTemplate(POSITION& pos) const {return (CDocTemplate*)m_templateList.GetNext(pos); }2.文檔模板類CDocTemplate文檔模板類CDocTemplate是一個抽象基類(這意味著不能直接用它來定義對象而必須用它的派生類),它定義了文檔模板的基本處理函數(shù)接口。對一個單文檔界面程序,需使用單文檔模板類CSingleDocTemplate,而對于一個多文檔界面程序,需使用多文檔模板類CMultipleDocTemplate。我們首先來看看CDocTemplate類的聲明:?
class CDocTemplate : public CCmdTarget {DECLARE_DYNAMIC(CDocTemplate)// Constructorsprotected:CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass);public:virtual void LoadTemplate();// Attributespublic:// setup for OLE containersvoid SetContainerInfo(UINT nIDOleInPlaceContainer);// setup for OLE serversvoid SetServerInfo(UINT nIDOleEmbedding, UINT nIDOleInPlaceServer = 0, CRuntimeClass* pOleFrameClass = NULL, CRuntimeClass* pOleViewClass = NULL);// iterating over open documentsvirtual POSITION GetFirstDocPosition() const = 0;virtual CDocument* GetNextDoc(POSITION& rPos) const = 0;// Operationspublic:virtual void AddDocument(CDocument* pDoc); // must overridevirtual void RemoveDocument(CDocument* pDoc); // must overrideenum DocStringIndex{windowTitle, // default window titledocName, // user visible name for default documentfileNewName, // user visible name for FileNew// for file based documents:filterName, // user visible name for FileOpenfilterExt, // user visible extension for FileOpen// for file based documents with Shell open support:regFileTypeId, // REGEDIT visible registered file type identifierregFileTypeName, // Shell visible registered file type name};virtual BOOL GetDocString(CString& rString, enum DocStringIndex index) const; // get one of the info stringsCFrameWnd* CreateOleFrame(CWnd* pParentWnd, CDocument* pDoc,BOOL bCreateView);// Overridablespublic:enum Confidence{noAttempt,maybeAttemptForeign,maybeAttemptNative,yesAttemptForeign,yesAttemptNative,yesAlreadyOpen};virtual Confidence MatchDocType(LPCTSTR lpszPathName,CDocument*& rpDocMatch);virtual CDocument* CreateNewDocument();virtual CFrameWnd* CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther);virtual void InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,BOOL bMakeVisible = TRUE);virtual BOOL SaveAllModified(); // for all documentsvirtual void CloseAllDocuments(BOOL bEndSession);virtual CDocument* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0;// open named file// if lpszPathName == NULL => create new file with this typevirtual void SetDefaultTitle(CDocument* pDocument) = 0;// Implementationpublic:BOOL m_bAutoDelete;virtual ~CDocTemplate();// back pointer to OLE or other server (NULL if none or disabled)CObject* m_pAttachedFactory;// menu & accelerator resources for in-place containerHMENU m_hMenuInPlace;HACCEL m_hAccelInPlace;// menu & accelerator resource for server editing embeddingHMENU m_hMenuEmbedding;HACCEL m_hAccelEmbedding;// menu & accelerator resource for server editing in-placeHMENU m_hMenuInPlaceServer;HACCEL m_hAccelInPlaceServer;#ifdef _DEBUGvirtual void Dump(CDumpContext&) const;virtual void AssertValid() const;#endifvirtual void OnIdle(); // for all documentsvirtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo);protected:UINT m_nIDResource; // IDR_ for frame/menu/accel as wellUINT m_nIDServerResource; // IDR_ for OLE inplace frame/menu/accelUINT m_nIDEmbeddingResource; // IDR_ for OLE open frame/menu/accelUINT m_nIDContainerResource; // IDR_ for container frame/menu/accelCRuntimeClass* m_pDocClass; // class for creating new documentsCRuntimeClass* m_pFrameClass; // class for creating new framesCRuntimeClass* m_pViewClass; // class for creating new viewsCRuntimeClass* m_pOleFrameClass; // class for creating in-place frameCRuntimeClass* m_pOleViewClass; // class for creating in-place viewCString m_strDocStrings; // '\n' separated names// The document names sub-strings are represented as _one_ string:// windowTitle\ndocName\n ... (see DocStringIndex enum) };文檔模板掛接了后面要介紹的文檔、視圖和框架窗口,使得它們得以互相關(guān)聯(lián)。通過文檔模板,程序確定了創(chuàng)建或打開一個文檔時,以什么樣的視圖和框架窗口來顯示。文檔模板依靠保存相互對應(yīng)的文檔、視圖和框架窗口的CRuntimeClass對象指針來實現(xiàn)上述掛接,這就是文檔模板類中的成員變量m_pDocClass、m_pFrameClass、m_pViewClass的由來。實際上,對m_pDocClass、m_pFrameClass、m_pViewClass的賦值在CDocTemplate類的構(gòu)造函數(shù)中實施:
CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass) {ASSERT_VALID_IDR(nIDResource);ASSERT(pDocClass == NULL || pDocClass->IsDerivedFrom(RUNTIME_CLASS(CDocument)));ASSERT(pFrameClass == NULL ||pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd)));ASSERT(pViewClass == NULL || pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));m_nIDResource = nIDResource;m_nIDServerResource = NULL;m_nIDEmbeddingResource = NULL;m_nIDContainerResource = NULL;m_pDocClass = pDocClass;m_pFrameClass = pFrameClass;m_pViewClass = pViewClass;m_pOleFrameClass = NULL;m_pOleViewClass = NULL;m_pAttachedFactory = NULL;m_hMenuInPlace = NULL;m_hAccelInPlace = NULL;m_hMenuEmbedding = NULL;m_hAccelEmbedding = NULL;m_hMenuInPlaceServer = NULL;m_hAccelInPlaceServer = NULL;// add to pStaticList if constructed as static instead of on heapif (CDocManager::bStaticInit){m_bAutoDelete = FALSE;if (CDocManager::pStaticList == NULL)CDocManager::pStaticList = new CPtrList;if (CDocManager::pStaticDocManager == NULL)CDocManager::pStaticDocManager = new CDocManager;CDocManager::pStaticList->AddTail(this);}else{m_bAutoDelete = TRUE; // usually allocated on the heapLoadTemplate();} }文檔模板類CDocTemplate還保存了它所支持的全部文檔類的信息,包括所支持文檔的文件擴(kuò)展名、文檔在框架窗口中的名字、圖標(biāo)等。CDocTemplate類的AddDocument、RemoveDocument成員函數(shù)使得CDocument* pDoc參數(shù)所指向的文檔歸屬于本文檔模板(通過將this指針賦值給pDoc所指向CDocument對象的m_pDocTemplate成員變量)或脫離與本文檔模板的關(guān)系:
void CDocTemplate::AddDocument(CDocument* pDoc) {ASSERT_VALID(pDoc);ASSERT(pDoc->m_pDocTemplate == NULL); // no template attached yetpDoc->m_pDocTemplate = this; } void CDocTemplate::RemoveDocument(CDocument* pDoc) {ASSERT_VALID(pDoc);ASSERT(pDoc->m_pDocTemplate == this); // must be attached to uspDoc->m_pDocTemplate = NULL; }而CDocTemplate類的CreateNewDocument成員函數(shù)則首先調(diào)用CDocument運行時類的CreateObject函數(shù)創(chuàng)建一個CDocument對象,再調(diào)用AddDocument成員函數(shù)將其歸屬于本文檔模板類:
CDocument* CDocTemplate::CreateNewDocument() {// default implementation constructs one from CRuntimeClassif (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; }文檔類對象由文檔模板類構(gòu)造生成,單文檔模板類CSingleDocTemplate只能生成一個文檔類對象,并用成員變量 m_pOnlyDoc 指向該對象;多文檔模板類可以生成多個文檔類對象,用成員變量 m_docList 指向文檔對象組成的鏈表。CSingleDocTemplate的構(gòu)造函數(shù)、AddDocument及RemoveDocument成員函數(shù)都在CDocTemplate類相應(yīng)函數(shù)的基礎(chǔ)上增加了對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 pleaseASSERT_VALID(pDoc);CDocTemplate::AddDocument(pDoc);m_pOnlyDoc = pDoc; } void CSingleDocTemplate::RemoveDocument(CDocument* pDoc) {ASSERT(m_pOnlyDoc == pDoc); // must be this oneASSERT_VALID(pDoc);CDocTemplate::RemoveDocument(pDoc);m_pOnlyDoc = NULL; }同樣,CMultiDocTemplate類的相關(guān)函數(shù)也需要對m_docList所指向的鏈表進(jìn)行操作(實際上AddDocument和RemoveDocument成員函數(shù)是文檔模板管理其所包含文檔的函數(shù)):
// 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 listm_docList.AddTail(pDoc); } void CMultiDocTemplate::RemoveDocument(CDocument* pDoc) {ASSERT_VALID(pDoc);CDocTemplate::RemoveDocument(pDoc);m_docList.RemoveAt(m_docList.Find(pDoc)); }由于CMultiDocTemplate類可包含多個文檔,依靠其成員函數(shù)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的這兩個函數(shù)實際上并無太大的意義,僅僅是MFC要玩的某種"招數(shù)",這個"招數(shù)"高明嗎?相信看完MFC的相關(guān)源代碼后你或許不會這么認(rèn)為,實際上CSingleDocTemplate的GetFirstDocPosition、GetNextDoc函數(shù)僅僅只能判斷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 documentASSERT(m_pOnlyDoc != NULL);pDoc = m_pOnlyDoc;}rPos = NULL; // no morereturn pDoc; }筆者認(rèn)為,MFC的設(shè)計者們將GetFirstDocPosition、GetNextDoc作為基類CDocTemplate的成員函數(shù)是不合理的,一種更好的做法是將GetFirstDocPosition、GetNextDoc移至CMultiDocTemplate派生類。
CDocTemplate還需完成對其對應(yīng)文檔的關(guān)閉與保存操作:
CDocTemplate還提供了框架窗口的創(chuàng)建和初始化函數(shù):
/ // Default frame creation CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther) {if (pDoc != NULL)ASSERT_VALID(pDoc);// create a frame wired to the specified documentASSERT(m_nIDResource != 0); // must have a resource ID to load fromCCreateContext 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 resourceif (!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 cleanupreturn NULL;}// it worked !return pFrame; } void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,BOOL bMakeVisible) {// just delagate to implementation in CFrameWndpFrame->InitialUpdateFrame(pDoc, bMakeVisible); }3. CWinApp與CDocManager/CDocTemplate類應(yīng)用程序CWinApp類對象與CDocManager和CDocTemplate類的關(guān)系是:CWinApp對象中包含一個CDocManager指針類型的共有數(shù)據(jù)成員m_pDocManager,CWinApp::InitInstance函數(shù)調(diào)用CWinApp::AddDocTemplate函數(shù)向鏈表m_templateList添加模板指針(實際上是調(diào)用前文所述CDocManager的AddDocTemplate函數(shù))。另外,CWinApp也提供了GetFirstDocTemplatePosition和GetNextDocTemplate函數(shù)實現(xiàn)來對m_templateList鏈表進(jìn)行訪問(實際上也是調(diào)用了前文所述CDocManager的GetFirstDocTemplatePosition、GetNextDocTemplate函數(shù))。我們僅摘取CWinApp類聲明的一小部分:
class CWinApp : public CWinThread {…CDocManager* m_pDocManager;// Running Operations - to be done on a running application// Dealing with document templatesvoid AddDocTemplate(CDocTemplate* pTemplate);POSITION GetFirstDocTemplatePosition() const;CDocTemplate* GetNextDocTemplate(POSITION& pos) const;// Dealing with filesvirtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named filevoid CloseAllDocuments(BOOL bEndSession); // close documents before exiting// Command Handlers protected:// map to the following for file new/openafx_msg void OnFileNew();afx_msg void OnFileOpen();int GetOpenDocumentCount();… };來看CWinApp派生類CSDIExampleApp(單文檔)、CMDIExampleApp(多文檔)的InitInstance成員函數(shù)的例子(僅僅摘取與文檔模板相關(guān)的部分):
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 frameRUNTIME_CLASS(CMDIExampleView));AddDocTemplate(pDocTemplate);… }讀者朋友,看完本次連載,也許您有許多不明白的地方,這是正常的。因為其所講解的內(nèi)容與后續(xù)幾次連載息息相關(guān),我們愈往后看,就會愈加清晰。對于本次連載的內(nèi)容,您只需要建立基本的印象。最初的淺嘗輒止是為了最終的深入脊髓!我們試圖對MFC的深層機(jī)理刨根究底,"撥開云霧見月明"的過程是艱辛的!
總結(jié)
以上是生活随笔為你收集整理的深入浅出MFC文档/视图架构之文档模板的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 全力做好2022年高考工作 交管局:视情
- 下一篇: Kindle中国明年停止电子书运营引热议