深入浅出MFC“文档/视图”架构(2)

――文档模板
作者:宋宝华 

1.文档模板管理者类CDocManager

在“文档/视图”架构的MFC程序中,提供了文档模板管理者类CDocManager,由它管理应用程序所包含的文档模板。我们先看看这个类的声明:

/ 
  

// CDocTemplate manager object 
  

  
  

class CDocManager : public CObject 
  

{ 
  

       DECLARE_DYNAMIC(CDocManager) 
  

public: 
  

  
  

// Constructor 
  

       CDocManager(); 
  

  
  

       //Document functions 
  

       virtual 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 file 
  

       virtual BOOL SaveAllModified(); // save before exit 
  

       virtual void CloseAllDocuments(BOOL bEndSession); // close documents before exiting 
  

       virtual int GetOpenDocumentCount(); 
  

  
  

       // helper for standard commdlg dialogs 
  

       virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle, 
  

                     DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate); 
  

  
  

//Commands 
  

       // Advanced: process async DDE request 
  

       virtual BOOL OnDDECommand(LPTSTR lpszCommand); 
  

       virtual void OnFileNew(); 
  

       virtual void OnFileOpen(); 
  

  
  

// Implementation 
  

protected: 
  

       CPtrList m_templateList; 
  

       int GetDocumentCount(); // helper to count number of total documents 
  

  
  

public: 
  

       static CPtrList* pStaticList;       // for static CDocTemplate objects 
  

       static BOOL bStaticInit;            // TRUE during static initialization 
  

       static CDocManager* pStaticDocManager;  // for static CDocTemplate objects 
  

  
  

public: 
  

       virtual ~CDocManager(); 
  

#ifdef _DEBUG 
  

       virtual void AssertValid() const; 
  

       virtual void Dump(CDumpContext& dc) const; 
  

#endif 
  

}; 
  

从上述代码可以看出,CDocManager类维护一个CPtrList类型的链表m_templateList(即文档模板链表,实际上,MFC的设计者在MFC的实现中大量使用了链表这种数据结构),CPtrList类型定义为: 
  

class CPtrList : public CObject 
  

{ 
  

       DECLARE_DYNAMIC(CPtrList) 
  

  
  

protected: 
  

       struct CNode 
  

       { 
  

              CNode* pNext; 
  

              CNode* pPrev; 
  

              void* data; 
  

       }; 
  

public: 
  

  
  

// Construction 
  

       CPtrList(int nBlockSize = 10); 
  

  
  

// Attributes (head and tail) 
  

       // count of elements 
  

       int GetCount() const; 
  

       BOOL IsEmpty() const; 
  

  
  

       // peek at head or tail 
  

       void*& 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 tail 
  

       POSITION AddHead(void* newElement); 
  

       POSITION AddTail(void* newElement); 
  

  
  

  
  

       // add another list of elements before head or after tail 
  

       void AddHead(CPtrList* pNewList); 
  

       void AddTail(CPtrList* pNewList); 
  

  
  

       // remove all elements 
  

       void RemoveAll(); 
  

  
  

       // iteration 
  

       POSITION 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 position 
  

       void*& GetAt(POSITION position); 
  

       void* GetAt(POSITION position) const; 
  

       void SetAt(POSITION pos, void* newElement); 
  

  
  

       void RemoveAt(POSITION position); 
  

  
  

       // inserting before or after a given position 
  

       POSITION 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 found 
  

       POSITION FindIndex(int nIndex) const; 
  

                                          // get the 'nIndex'th element (may return NULL) 
  

  
  

// Implementation 
  

protected: 
  

       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 _DEBUG 
  

       void Dump(CDumpContext&) const; 
  

       void AssertValid() const; 
  

#endif 
  

       // local typedefs for class templates 
  

       typedef void* BASE_TYPE; 
  

       typedef void* BASE_ARG_TYPE; 
  

}; 
  

很显然,CPtrList是对链表结构体 
  

struct CNode 
  

{ 
  

       CNode* pNext; 
  

       CNode* pPrev; 
  

       void* data; 
  

};



本身及其GetNext、GetPrev、GetAt、SetAt、RemoveAt、InsertBefore、InsertAfter、Find、FindIndex等各种操作的封装。

作为一个抽象的链表类型,CPtrList并未定义其中节点的具体类型,而以一个void指针(struct CNode 中的void* data)巧妙地实现了链表节点成员具体类型的“模板”化。很显然,在Visual C++6.0开发的年代,C++语言所具有的语法特征“模板”仍然没有得到广泛的应用。

而CDocManager类的成员函数

virtual void AddDocTemplate(CDocTemplate* pTemplate); 
  

virtual POSITION GetFirstDocTemplatePosition() const; 
  

virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const; 
  

则完成对m_TemplateList链表的添加及遍历操作的封装,我们来看看这三个函数的源代码: 
  

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 list 
  

              pTemplate->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是一个抽象基类(这意味着不能直接用它来定义对象而必须用它的派生类),它定义了文档模板的基本处理函数接口。对一个单文档界面程序,需使用单文档模板类CSingleDocTemplate,而对于一个多文档界面程序,需使用多文档模板类CMultipleDocTemplate。我们首先来看看CDocTemplate类的声明: 
  

class CDocTemplate : public CCmdTarget 
  

{ 
  

       DECLARE_DYNAMIC(CDocTemplate) 
  

  
  

// Constructors 
  

protected: 
  

       CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, 
  

              CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass); 
  

  
  

public: 
  

       virtual void LoadTemplate(); 
  

  
  

// Attributes 
  

public: 
  

       // setup for OLE containers 
  

       void SetContainerInfo(UINT nIDOleInPlaceContainer); 
  

  
  

       // setup for OLE servers 
  

       void SetServerInfo(UINT nIDOleEmbedding, UINT nIDOleInPlaceServer = 0, 
  

              CRuntimeClass* pOleFrameClass = NULL, CRuntimeClass* pOleViewClass = NULL); 
  

  
  

       // iterating over open documents 
  

       virtual POSITION GetFirstDocPosition() const = 0; 
  

       virtual CDocument* GetNextDoc(POSITION& rPos) const = 0; 
  

  
  

// Operations 
  

public: 
  

       virtual void AddDocument(CDocument* pDoc);      // must override 
  

       virtual void RemoveDocument(CDocument* pDoc);   // must override 
  

  
  

       enum DocStringIndex 
  

       { 
  

              windowTitle,        // default window title 
  

              docName,            // user visible name for default document 
  

              fileNewName,        // user visible name for FileNew 
  

              // for file based documents: 
  

              filterName,         // user visible name for FileOpen 
  

              filterExt,          // user visible extension for FileOpen 
  

              // for file based documents with Shell open support: 
  

              regFileTypeId,      // REGEDIT visible registered file type identifier 
  

              regFileTypeName,    // Shell visible registered file type name 
  

       }; 
  

       virtual BOOL GetDocString(CString& rString, 
  

              enum DocStringIndex index) const; // get one of the info strings 
  

       CFrameWnd* CreateOleFrame(CWnd* pParentWnd, CDocument* pDoc, 
  

              BOOL bCreateView); 
  

  
  

// Overridables 
  

public: 
  

       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 documents 
  

       virtual void CloseAllDocuments(BOOL bEndSession); 
  

       virtual CDocument* OpenDocumentFile( 
  

              LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0; 
  

                                   // open named file 
  

                                   // if lpszPathName == NULL => create new file with this type 
  

       virtual void SetDefaultTitle(CDocument* pDocument) = 0; 
  

  
  

// Implementation 
  

public: 
  

       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 container 
  

       HMENU m_hMenuInPlace; 
  

       HACCEL m_hAccelInPlace; 
  

  
  

       // menu & accelerator resource for server editing embedding 
  

       HMENU m_hMenuEmbedding; 
  

       HACCEL m_hAccelEmbedding; 
  

  
  

       // menu & accelerator resource for server editing in-place 
  

       HMENU m_hMenuInPlaceServer; 
  

       HACCEL m_hAccelInPlaceServer; 
  

  
  

#ifdef _DEBUG 
  

       virtual void Dump(CDumpContext&) const; 
  

       virtual void AssertValid() const; 
  

#endif 
  

       virtual void OnIdle();             // for all documents 
  

       virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, 
  

              AFX_CMDHANDLERINFO* pHandlerInfo); 
  

  
  

protected: 
  

       UINT m_nIDResource;                 // IDR_ for frame/menu/accel as well 
  

       UINT m_nIDServerResource;           // IDR_ for OLE inplace frame/menu/accel 
  

       UINT m_nIDEmbeddingResource;        // IDR_ for OLE open frame/menu/accel 
  

       UINT m_nIDContainerResource;        // IDR_ for container frame/menu/accel 
  

  
  

       CRuntimeClass* m_pDocClass;         // class for creating new documents 
  

       CRuntimeClass* m_pFrameClass;       // class for creating new frames 
  

       CRuntimeClass* m_pViewClass;        // class for creating new views 
  

       CRuntimeClass* m_pOleFrameClass;    // class for creating in-place frame 
  

       CRuntimeClass* m_pOleViewClass;     // class for creating in-place view 
  

  
  

       CString m_strDocStrings;    // '\n' separated names 
  

              // The document names sub-strings are represented as _one_ string: 
  

              // windowTitle\ndocName\n ... (see DocStringIndex enum) 
  

}; 
  

文档模板挂接了后面要介绍的文档、视图和框架窗口,使得它们得以互相关联。通过文档模板,程序确定了创建或打开一个文档时,以什么样的视图和框架窗口来显示。文档模板依靠保存相互对应的文档、视图和框架窗口的CRuntimeClass对象指针来实现上述挂接,这就是文档模板类中的成员变量m_pDocClass、m_pFrameClass、m_pViewClass的由来。实际上,对m_pDocClass、m_pFrameClass、m_pViewClass的赋值在CDocTemplate类的构造函数中实施: 
  

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 heap 
  

       if (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 heap 
  

              LoadTemplate(); 
  

       } 
  

} 
  

文档模板类CDocTemplate还保存了它所支持的全部文档类的信息,包括所支持文档的文件扩展名、文档在框架窗口中的名字、图标等。 
  

CDocTemplate类的AddDocument、RemoveDocument成员函数使得CDocument* pDoc参数所指向的文档归属于本文档模板(通过将this指针赋值给pDoc所指向CDocument对象的m_pDocTemplate成员变量)或脱离与本文档模板的关系: 
  

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(); 
  

       } 
  

}



前文我们提到,由于MFC的设计者将CSingleDocTemplate和CMultiDocTemplate的行为未进行规范的区分,它对仅仅对应一个文档的CSingleDocTemplate也提供了所谓的GetFirstDocPosition、GetNextDoc遍历操作,所以基类CDocTemplate的SaveAllModified和CloseAllDocuments函数(都是遍历)就可统一CSingleDocTemplate和CMultiDocTemplate两个本身并不相同类的SaveAllModified和CloseAllDocuments行为(实际上,对于CSingleDocTemplate而言,SaveAllModified和CloseAllDocuments中的“All”是没有太大意义的。教室里有1个老师和N个同学,老师可以对同学们说“所有同学”,而学生对老师说“所有老师”相信会被当成神经病)。MFC的设计者们特意使用了“将错就错”的方法意图简化CSingleDocTemplate和CMultiDocTemplate类的设计,读者朋友可以不认同他们的做法。

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类声明的一小部分:

c

lass 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); 
  

… 
  

}



读者朋友,看完本次连载,也许您有许多不明白的地方,这是正常的。因为其所讲解的内容与后续几次连载息息相关,我们愈往后看,就会愈加清晰。对于本次连载的内容,您只需要建立基本的印象。最初的浅尝辄止是为了最终的深入脊髓!


转载于:https://blog.51cto.com/21cnbao/120813