CDocManager

    技术2022-05-19  16

      在"文档/视图"架构的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) - dont 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 nIndexth 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; // separated names   // The document names sub-strings are represented as _one_ string:   // windowTitle docName ... (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. ");   ASSERT(FALSE);   return NULL;  }  CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();  if (pDocument == NULL)  {   TRACE1("Warning: Dynamic create of document type %hs failed. ",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. ");   ASSERT(FALSE);   return NULL;  }  CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();  if (pFrame == NULL)  {   TRACE1("Warning: Dynamic create of frame %hs failed. ",m_pFrameClass->m_lpszClassName);   return NULL;  }  ASSERT_KINDOF(CFrameWnd, pFrame);

     if (context.m_pNewViewClass == NULL)   TRACE0("Warning: creating frame with no default view. ");

     // create new from resource  if (!pFrame->LoadFrame(m_nIDResource,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles NULL, &context))  {   TRACE0("Warning: CDocTemplate couldnt create a frame. ");   // 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的深层机理刨根究底,"拨开云雾见月明"的过程是艰辛的!


    最新回复(0)