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

简介:
深入浅出 MFC “文档 / 视图”架构( 3
――文档
作者:宋宝华  e-mail:[email]21cnbao@21cn.com[/email]
1. 文档类 CDocument
在“文档/视图”架构的MFC程序中,文档是一个CDocument派生对象,它负责存储应用程序的数据,并把这些信息提供给应用程序的其余部分。CDocument类对文档的建立及归档提供支持并提供了应用程序用于控制其数据的接口,类CDocument的声明如下:
/////////////////////////////////////////////////////////////////////////////
// class CDocument is the main document data abstraction
class CDocument : public CCmdTarget
{
       DECLARE_DYNAMIC(CDocument)
 
public:
// Constructors
       CDocument();
 
// Attributes
public:
       const CString& GetTitle() const;
       virtual void SetTitle(LPCTSTR lpszTitle);
       const CString& GetPathName() const;
       virtual void SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU = TRUE);
 
       CDocTemplate* GetDocTemplate() const;
       virtual BOOL IsModified();
       virtual void SetModifiedFlag(BOOL bModified = TRUE);
 
// Operations
       void AddView(CView* pView);
       void RemoveView(CView* pView);
       virtual POSITION GetFirstViewPosition() const;
       virtual CView* GetNextView(POSITION& rPosition) const;
 
       // Update Views (simple update - DAG only)
       void UpdateAllViews(CView* pSender, LPARAM lHint = 0L,
              CObject* pHint = NULL);
 
// Overridables
       // Special notifications
       virtual void OnChangedViewList(); // after Add or Remove view
       virtual void DeleteContents(); // delete doc items etc
 
       // File helpers
       virtual BOOL OnNewDocument();
       virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
       virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
       virtual void OnCloseDocument();
       virtual void ReportSaveLoadException(LPCTSTR lpszPathName,
                            CException* e, BOOL bSaving, UINT nIDPDefault);
       virtual CFile* GetFile(LPCTSTR lpszFileName, UINT nOpenFlags,
              CFileException* pError);
       virtual void ReleaseFile(CFile* pFile, BOOL bAbort);
 
       // advanced overridables, closing down frame/doc, etc.
       virtual BOOL CanCloseFrame(CFrameWnd* pFrame);
       virtual BOOL SaveModified(); // return TRUE if ok to continue
       virtual void PreCloseFrame(CFrameWnd* pFrame);
 
// Implementation
protected:
       // default implementation
       CString m_strTitle;
       CString m_strPathName;
       CDocTemplate* m_pDocTemplate;
       CPtrList m_viewList;                // list of views
       BOOL m_bModified;                   // changed since last saved
 
public:
       BOOL m_bAutoDelete;     // TRUE => delete document when no more views
       BOOL m_bEmbedded;       // TRUE => document is being created by OLE
 
#ifdef _DEBUG
       virtual void Dump(CDumpContext&) const;
       virtual void AssertValid() const;
#endif //_DEBUG
       virtual ~CDocument();
 
       // implementation helpers
       virtual BOOL DoSave(LPCTSTR lpszPathName, BOOL bReplace = TRUE);
       virtual BOOL DoFileSave();
       virtual void UpdateFrameCounts();
       void DisconnectViews();
       void SendInitialUpdate();
 
       // overridables for implementation
       virtual HMENU GetDefaultMenu(); // get menu depending on state
       virtual HACCEL GetDefaultAccelerator();
       virtual void OnIdle();
       virtual void OnFinalRelease();
 
       virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,
              AFX_CMDHANDLERINFO* pHandlerInfo);
       friend class CDocTemplate;
 
protected:
       // file menu commands
       //{{AFX_MSG(CDocument)
       afx_msg void OnFileClose();
       afx_msg void OnFileSave();
       afx_msg void OnFileSaveAs();
       //}}AFX_MSG
       // mail enabling
       afx_msg void OnFileSendMail();
       afx_msg void OnUpdateFileSendMail(CCmdUI* pCmdUI);
       DECLARE_MESSAGE_MAP()
};
一个文档可以有多个视图,每一个文档都维护一个与之相关视图的链表(CptrList类型的 m_viewList实例)。CDocument::AddView将一个视图连接到文档上,并将视图的文档指针指向该文档:
void CDocument::AddView(CView* pView)
{
       ASSERT_VALID(pView);
       ASSERT(pView->m_pDocument == NULL); // must not be already attached
       ASSERT(m_viewList.Find(pView, NULL) == NULL);   // must not be in list
 
       m_viewList.AddTail(pView);
       ASSERT(pView->m_pDocument == NULL); // must be un-attached
       pView->m_pDocument = this;
 
       OnChangedViewList();    // must be the last thing done to the document
}
CDocument::RemoveView则完成与CDocument::AddView相反的工作:
void CDocument::RemoveView(CView* pView)
{
       ASSERT_VALID(pView);
       ASSERT(pView->m_pDocument == this); // must be attached to us
 
       m_viewList.RemoveAt(m_viewList.Find(pView));
       pView->m_pDocument = NULL;
 
       OnChangedViewList();    // must be the last thing done to the document
}
CDocument::AddViewCDocument::RemoveView函数可以看出,在与文档关联的视图被移走或新加入时CDocument::OnChangedViewList将被调用:
void CDocument::OnChangedViewList()
{
       // if no more views on the document, delete ourself
       // not called if directly closing the document or terminating the app
       if (m_viewList.IsEmpty() && m_bAutoDelete)
       {
              OnCloseDocument();
              return;
       }
 
       // update the frame counts as needed
       UpdateFrameCounts();
}
CDocument::DisconnectViews将所有的视图都与文档“失连”:
void CDocument::DisconnectViews()
{
       while (!m_viewList.IsEmpty())
       {
              CView* pView = (CView*)m_viewList.RemoveHead();
              ASSERT_VALID(pView);
              ASSERT_KINDOF(CView, pView);
              pView->m_pDocument = NULL;
       }
}
实际上,类CDocument对视图的管理与类CDocManager对文档模板的管理及CDocTemplate对文档的管理非常类似,少不了的,类CDocument中可遍历对应的视图(出现GetFirstXXXGetNextXXX两个函数):
POSITION CDocument::GetFirstViewPosition() const
{
       return m_viewList.GetHeadPosition();
}
 
CView* CDocument::GetNextView(POSITION& rPosition) const
{
       ASSERT(rPosition != BEFORE_START_POSITION);
              // use CDocument::GetFirstViewPosition instead !
       if (rPosition == NULL)
              return NULL;    // nothing left
       CView* pView = (CView*)m_viewList.GetNext(rPosition);
       ASSERT_KINDOF(CView, pView);
       return pView;
}
CDocument::GetFileCDocument::ReleaseFile函数完成对参数lpszFileName指定文档的打开与关闭操作:
CFile* CDocument::GetFile(LPCTSTR lpszFileName, UINT nOpenFlags,
       CFileException* pError)
{
       CMirrorFile* pFile = new CMirrorFile;
       ASSERT(pFile != NULL);
       if (!pFile->Open(lpszFileName, nOpenFlags, pError))
       {
              delete pFile;
              pFile = NULL;
       }
       return pFile;
}
 
void CDocument::ReleaseFile(CFile* pFile, BOOL bAbort)
{
       ASSERT_KINDOF(CFile, pFile);
       if (bAbort)
              pFile->Abort(); // will not throw an exception
       else
              pFile->Close();
       delete pFile;
}
CDocument类的OnNewDocumentOnOpenDocumentOnSaveDocumentOnCloseDocument这一组成员函数用于创建、打开、保存或关闭一个文档。在这一组函数中,上面的CDocument::GetFileCDocument::ReleaseFile两个函数得以调用:
BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)
{
       if (IsModified())
              TRACE0("Warning: OnOpenDocument replaces an unsaved document.\n");
 
       CFileException fe;
       CFile* pFile = GetFile(lpszPathName,
              CFile::modeRead|CFile::shareDenyWrite, &fe);
       if (pFile == NULL)
       {
              ReportSaveLoadException(lpszPathName, &fe,
                     FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
              return FALSE;
       }
 
       DeleteContents();
       SetModifiedFlag();  // dirty during de-serialize
 
       CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);
       loadArchive.m_pDocument = this;
       loadArchive.m_bForceFlat = FALSE;
       TRY
       {
              CWaitCursor wait;
              if (pFile->GetLength() != 0)
                     Serialize(loadArchive);     // load me
              loadArchive.Close();
              ReleaseFile(pFile, FALSE);
       }
       CATCH_ALL(e)
       {
              ReleaseFile(pFile, TRUE);
              DeleteContents();   // remove failed contents
 
              TRY
              {
                     ReportSaveLoadException(lpszPathName, e,
                            FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
              }
              END_TRY
              DELETE_EXCEPTION(e);
              return FALSE;
       }
       END_CATCH_ALL
 
       SetModifiedFlag(FALSE);     // start off with unmodified
 
       return TRUE;
}
打开文档的函数CDocument::OnOpenDocument完成的工作包括如下几步:
1)打开文件对象;
2)调用DeleteDontents()
3)建立与此文件对象相关联的CArchive对象;
4)调用应用程序文档对象的Serialize()函数;
5)关闭CArchive对象、文件对象。
BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName)
{
       CFileException fe;
       CFile* pFile = NULL;
       pFile = GetFile(lpszPathName, CFile::modeCreate |
              CFile::modeReadWrite | CFile::shareExclusive, &fe);
 
       if (pFile == NULL)
       {
              ReportSaveLoadException(lpszPathName, &fe,
                     TRUE, AFX_IDP_INVALID_FILENAME);
              return FALSE;
       }
 
       CArchive saveArchive(pFile, CArchive::store | CArchive::bNoFlushOnDelete);
       saveArchive.m_pDocument = this;
       saveArchive.m_bForceFlat = FALSE;
       TRY
       {
              CWaitCursor wait;
              Serialize(saveArchive);     // save me
              saveArchive.Close();
              ReleaseFile(pFile, FALSE);
       }
       CATCH_ALL(e)
       {
              ReleaseFile(pFile, TRUE);
 
              TRY
              {
                     ReportSaveLoadException(lpszPathName, e,
                            TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);
              }
              END_TRY
              DELETE_EXCEPTION(e);
              return FALSE;
       }
       END_CATCH_ALL
 
       SetModifiedFlag(FALSE);     // back to unmodified
 
       return TRUE;        // success
}
保存文档的函数CDocument::OnSaveDocument完成的工作包括如下几步:
1)创建或打开文件对象;
2)建立相对应的CArchive对象;
3)调用应用程序文档对象的序列化函数Serialize()
4)关闭文件对象、CArchive对象;
5)设置文件未修改标志。
void CDocument::OnCloseDocument()
       // must close all views now (no prompting) - usually destroys this
{
       // destroy all frames viewing this document
       // the last destroy may destroy us
       BOOL bAutoDelete = m_bAutoDelete;
       m_bAutoDelete = FALSE;  // don't destroy document while closing views
       while (!m_viewList.IsEmpty())
       {
              // get frame attached to the view
              CView* pView = (CView*)m_viewList.GetHead();
              ASSERT_VALID(pView);
              CFrameWnd* pFrame = pView->GetParentFrame();
              ASSERT_VALID(pFrame);
 
              // and close it
              PreCloseFrame(pFrame);
              pFrame->DestroyWindow();
                     // will destroy the view as well
       }
       m_bAutoDelete = bAutoDelete;
 
       // clean up contents of document before destroying the document itself
       DeleteContents();
 
       // delete the document if necessary
       if (m_bAutoDelete)
              delete this;
}
CDocument::OnCloseDocument函数的程序流程为:
1)通过文档对象所对应的视图,得到显示该文档视图的框架窗口的指针;
2)关闭并销毁这些框架窗口;
3)判断文档对象的自动删除变量m_bAutoDelete是否为真,如果为真,则以delete this语句销毁文档对象本身。
实际上,真正实现文档存储和读取(相对于磁盘)的函数是Serialize,这个函数通常会被CDocument的派生类重载(加入必要的代码,用以保存对象的数据成员到CArchive对象以及从CArchive对象载入对象的数据成员状态):
void CExampleDoc::Serialize(CArchive& ar)
{
       if (ar.IsStoring())
       {
              // TODO: add storing code here
              ar << var1 << var2;
       }
       else
       {
              // TODO: add loading code here
              var2 >> var1 >> ar;
       }
}
地球人都知道,文档与视图进行通信的方式是调用文档类的UpdateAllViews函数:
void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)
       // walk through all views
{
       ASSERT(pSender == NULL || !m_viewList.IsEmpty());
              // must have views if sent by one of them
 
       POSITION pos = GetFirstViewPosition();
       while (pos != NULL)
       {
              CView* pView = GetNextView(pos);
              ASSERT_VALID(pView);
              if (pView != pSender)
                     pView->OnUpdate(pSender, lHint, pHint);
       }
}
UpdateAllViews函数遍历视图列表,对每个视图都调用其OnUpdate函数实现视图的更新显示。
2. 文档的 OPEN/NEW
从连载2可以看出,在应用程序类CWinapp的声明中包含文件的NewOpen函数:
afx_msg void OnFileNew();
afx_msg void OnFileOpen();
而在文档模板管理者类CDocManager中也包含文件的NewOpen函数:
virtual void OnFileNew();
virtual void OnFileOpen();
virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named file
而文档模板类CDocTemplate也不例外:
virtual CDocument* OpenDocumentFile(
       LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0;
       // open named file
       // if lpszPathName == NULL => create new file with this type
       virtual CDocument* CreateNewDocument();
复杂的是,我们在CDocument类中再次看到了NewOpen相关函数:
virtual BOOL OnNewDocument();
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
在这众多的函数中,究竟文档的创建者和打开者是谁?“文档/视图”框架程序“File”菜单上的“New”和“Open”命令究竟对应着怎样的函数调用行为?这一切都使我们陷入迷惘!
实际上“文档/视图”框架程序新文档及其关联视图和框架窗口的创建是应用程序对象、文档模板、新创建的文档和新创建的框架窗口相互合作的结果。具体而言,应用程序对象创建了文档模板;文档模板则创建了文档及框架窗口;框架窗口创建了视图。
在用户按下ID_FILE_OPENID_FILE_NEW菜单(或工具栏)命令后,CWinApp(派生)类的OnFileNewOnFileOpen函数首先被执行,其进行的行为是选择合适的文档模板,如图3.1所示。
3.1文档模板的选择
实际上,图3.1中所示的“使用文件扩展名选择文档模板”、“是一个文档模板吗?”的行为都要借助于CDocManager类的相关函数,因为只有CDocManager类才维护了文档模板的列表。CDocManager::OnFileNew的行为可描述为:
void CDocManager::OnFileNew()
{
  if (m_templateList.IsEmpty())
  {
    ...
    return ;
  }
  //取第一个文档模板的指针
  CDocTemplate *pTemplate = (CDocTemplate*)m_templateList.GetHead();
  if (m_templateList.GetCount() > 1)
  {
    // 如果多于一个文档模板,弹出对话框提示用户选择
    CNewTypeDlg dlg(&m_templateList);
    int nID = dlg.DoModal();
    if (nID == IDOK)
      pTemplate = dlg.m_pSelectedTemplate;
    else
      return ;
    // none - cancel operation
  }
  
  //参数为NULL的时候OpenDocument File会新建一个文件
  pTemplate->OpenDocumentFile(NULL);
}
之后,文档模板类的virtual CDocument* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0函数进行文档的创建工作,如果lpszPathName == NULL,是文档New行为;相反,则是Open行为。在创建框架后,文档模板根据是Open还是New行为分别调用CDocumentOnOpenDocumentOnNewDocument函数。图3.2描述了整个过程。
3.2文档、框架窗口的创建顺序
而图3.3则给出了视图的创建过程。
3.3视图的创建顺序
3.1~3.3既描述了文档/视图框架对ID_FILE_OPENID_FILE_NEW命令的响应过程,又描述了文档、框架窗口及视图的创建。的确,是无法单独描述文档的NewOpen行为的,因为它和其他对象的创建交错纵横。
相信,随着我们进一步阅读后续连载,会对上述过程有更清晰的认识。



 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120305,如需转载请自行联系原作者

相关文章
|
2月前
|
SQL NoSQL 前端开发
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
|
7月前
|
缓存 运维 NoSQL
GitHub开源大厂缓存架构Redis优化的文档被警告,900页全是干货
掌握Redis对Java程序员来说很有必要了。实际上,很少有人真的掌握了Redis的全部技巧,有些甚至连面试题都很难应付。那么,如何全面系统地学习Redis呢?
|
7月前
|
设计模式 架构师 Java
阿里P8架构师都要学习研究的java加强版23种设计模式神级PDF文档
说在前面的话 Java作为老牌纯正的编程语言,在规范性上有着天然优势。因此本版的设计模式讲解全部用Java语言来描述,并针对Java语言的特性对讲解内容做了相当大的改动。 不知道大家是否听过编程界的一段话:掌握设计模式相当于华山派的"气宗",是程序员的内功修为,虽然在同样的学习时间下,类似Python这种"剑宗"的开发模式见效更快,但是长远来看,"气宗"才是走向软件架构师以上级别的必由之路。 所以,掌握气宗就掌握了编程命脉,然而学习设计模式有四大境界: 接下来给大家分享的就是java溢彩加强版大话设计模式包含的内容知识点。 总目录 主要内容 本文是百万销量的经典畅销书《
131 0
|
2月前
|
存储 设计模式 前端开发
请解释 Web 应用程序的 MVC(模型-视图-控制器)架构。
【2月更文挑战第26天】【2月更文挑战第89篇】请解释 Web 应用程序的 MVC(模型-视图-控制器)架构。
|
3月前
MFC应用程序对话框架构
MFC应用程序对话框架构
16 0
|
3月前
MFC单文档视图架构
MFC单文档视图架构
14 0
|
3月前
|
JSON 前端开发 Java
SpringMVC的架构有什么优势?——视图与模型(二)
SpringMVC的架构有什么优势?——视图与模型(二)
|
5月前
|
开发框架 架构师 Java
Java程序员不掌握SpringBoot怎么进大厂,阿里架构师推荐实战文档
Spring Boot作为Java编程语言的一个全新开发框架,在国内外才刚刚兴起时,还未得到普及使用。
|
5月前
|
安全 Java 应用服务中间件
全网最新架构实战文档:高并发+分布式+微服务+SpringBoot+Nginx
关于一线互联网大厂网站的一些特点:用户多,分布广泛、大流量,高并发、海量数据,服务高可用、安全环境恶劣,易受网络攻击、功能多,变更快,频繁发布、从小到大,渐进发展、以用户为中心。
|
6月前
|
算法 架构师 Java
阿里P8架构师爆肝分享内部开源的JVM垃圾回收PDF文档,共23.3W字
说在前面 本文讲解的内容是关于垃圾回收(Garbage Collection,GC)的文档 ,为什么要写关于垃圾回收的文档呢? 首先,垃圾回收对应用影响很大,主要表现在应用停顿时间、吞吐量、资源使用等方面,开发者选择一种语言时考虑的一个重要因素就是该语言是否支持垃圾回收以及支持哪些垃圾回收实现(要综合考虑开发难度、效率和运行效率)。 其次,Hotspot是最流行的Java虚拟机(Java Virtual Machine,JVM。 本文使用JVM指代Hotspot虚拟机),垃圾回收是Java虚拟机最重要的组成部分,也是最复杂的部分之一。以JDK 8为例,共计支持5种垃圾回收实现,提供了超过
56 0