深入浅出MFC文档/视图架构之文档模板

来源:互联网 发布:淘宝客优惠券插件 编辑:程序博客网 时间:2024/06/07 08:10

文档模板管理者类CDocManager

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

 

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类的声明:

 

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 creationCFrameWnd* 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 stylesNULL, &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 Handlersprotected: // 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 windowRUNTIME_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); …}

 

原创粉丝点击