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

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::AddView和CDocument::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中可遍历对应的视图(出现GetFirstXXX和GetNextXXX两个函数):

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::GetFile和CDocument::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类的OnNewDocument、OnOpenDocument、OnSaveDocument及OnCloseDocument这一组成员函数用于创建、打开、保存或关闭一个文档。在这一组函数中,上面的CDocument::GetFile和CDocument::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的声明中包含文件的New和Open函数:
afx_msg void OnFileNew();
afx_msg void OnFileOpen();
  而在文档模板管理者类CDocManager中也包含文件的New和Open函数:
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类中再次看到了New和Open相关函数:
virtual BOOL OnNewDocument();
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
  在这众多的函数中,究竟文档的创建者和打开者是谁?"文档/视图"框架程序"File"菜单上的"New"和"Open"命令究竟对应着怎样的函数调用行为?这一切都使我们陷入迷惘!
  实际上"文档/视图"框架程序新文档及其关联视图和框架窗口的创建是应用程序对象、文档模板、新创建的文档和新创建的框架窗口相互合作的结果。具体而言,应用程序对象创建了文档模板;文档模板则创建了文档及框架窗口;框架窗口创建了视图。
  在用户按下ID_FILE_OPEN及ID_FILE_NEW菜单(或工具栏)命令后,CWinApp(派生)类的OnFileNew、OnFileOpen函数首先被执行,其进行的行为是选择合适的文档模板,如图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行为分别调用CDocument的OnOpenDocument、OnNewDocument函数。图3.2描述了整个过程。
图3.2文档、框架窗口的创建顺序
而图3.3则给出了视图的创建过程。
图3.3视图的创建顺序
  图3.1~3.3既描述了文档/视图框架对ID_FILE_OPEN及ID_FILE_NEW命令的响应过程,又描述了文档、框架窗口及视图的创建。的确,是无法单独描述文档的New和Open行为的,因为它和其他对象的创建交错纵横。
  相信,随着我们进一步阅读后续连载,会对上述过程有更清晰的认识。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一本填补“使用向导”类的VisualC++书籍、产品文档以及MFC源代码之间空隙的MFC书籍。本书是了解MFC内幕的向导,提供了关于那些没有文档记录的MFC类、实用函数和数据成员的独一无二并且透彻的信息,介绍了有用的编码技巧,并对MFC各个类之间的协作方式进行了重要的分析。 本书的第一部分包含了核心的MFC图形用户界面类以及支持它们的类,第二部分包含了像OLE这种扩展基本Windows支持的主题。如果做到以下几点,你就可以成为一位透彻理解MFC实现细节的专家:探索MFC文档/视图结构的内幕,从而学习视图同步、打印和打印预览;更深入地了解MFC序列化中那些没有文档记录的方面和一些没有文档记录的类,例如CPreview、CPreviewDC、CMirrorFile以及CDockBar等等;最后理解MFC 和OLE是如何共同运作的,以及OLE控悠扬是如何实现的;积累技巧,学会自己研究和理解MFC源代码。 目录: 前言 致谢 简介 第1章 MFC的概念性总括 面向对象编程的一些背景 面向对象编程术语 通常的对象 对象与C++ 为什么使用OOP 应用程序框架与MFC MFC要点之旅 结语 第2章 基本的Windows支持 MFC与C/SDK 基本的MFC应用程序组件 现在,找到WinMain() 一些其他隐藏的信息 MFC对GDI的支持 结语 第3章 MFC中的消息处理 CCmdTarget和消息映射表 窗口消息 MFC消息映射内幕 MFC如何使用消息映射表 进入消息循环:PreTranslateMessage() 结语 第4章 MFC实用类 简单值类型 MFC的集合类 CFile家族:MFC对文件的访问 CExcephon:提供更好的错误处理 结语 第5章 CObject 使用CObject的代价 CObject的特性 宏的介绍 运行时类的信息 MFC中的持续性 CObject对诊断的支持 CObject的诊断支持内幕 组合在一起 投入使用 是否值得 结语 第6章 MFC对话框和控件类 CDialog:模态MFC对话框和非模态MFC对话框 MFC公用对话框 OLE对话框 属性页(也称带标签的对话框) MFC控件类 结语 第7章 MFC文档视图结构 为什么要用文档视图 其他原因 旧的方法 体系结构 文档视图结构内幕 文档舰图内幕再览 结语 第8章 高级文档舰图结构内幕 CMirrorFile CView打印 CView对打印预览支持的内幕 CView的派生类:CScrollView CView的另一个派生类:CCtrlView 结语 第9章 MFC的增强型用户界面类 CSplitterWnd:MFC分割窗口 MFC的CControlBar体系结构 CMiniFrameWnd MFC的MRU文件链表实现 结语 第10章 MFC的DLL与线程 理解状态 MFC的DLL MFC线程 结语 下一章 第11章 用MFC实现COM MFC和OLE COM 何为COM类 COM接口 GUID 剖析IUnknown接口 COM对象服务器 拥有多个接口的COM类 MFCCOM类 使用MFC创建CoMath MFCCOM和接口映射宏 使用MFC的CoMath类 完成服务器的设计 MFC对类厂的支持 结语 第12章 统一数据传输和MFC 历史回顾 重要的结构 IDataObject接口 OLE剪贴板 MFC的IDataObjeot类 延迟供应 深入了解MFC的IDataObject类 OLE拖放 结语 第13章 使用MFC实现OLE文档 OLE文档101 MFC对OLE文档的支持 使用MFC实现OLE文档服务器 容器朋艮务器的协调工作 使条目无效 保存容器的文档 装载OLE文档 结语 第14章 MFC与自动化 自动化的历史 自动化的功能 使用MFC实现自动化应用程序 自动化的工作机制 COM接口与自动化 实现自动化的另外一种方法:使用类型信息 MFC与自动化 结语:使用“MFC方式”的结果 第15章OLE控件 VBX及其缺陷 OLE控件 写一个OLE控件 在工程里使用OLE控件 它是如何工作的 MFC和OLE控件的容器 OLE控件的生存周期 OLE连接 OLE控件的事件 MFC如何处理事件 技巧:在一个视图中加入一个事件接收器 OLE控件的属性页 结语 附录A MFC源代码导读 MFC编码技术 探索MFC的工具 MFC源代码指南 愉快的旅途 附录B 本书的示例代码 术语表
MFC(Microsoft Foundation Classes)是一套用于开发Windows应用程序的C++库,其中的文档-视图结构(Document-View Architecture)是一种常见的设计模式,用于实现应用程序的数据和用户界面的分离。 在MFC中,文档Document)是应用程序的数据模型,而视图(View)是用户界面模型。文档负责管理数据的读写和操作逻辑,而视图则负责展示数据,并与用户交互。文档视图之间通过文档交换框架(Document-View Framework)进行通信。 文档视图之间的关系是一对多的关系,即一个文档可以有多个视图。这种设计模式的好处是,可以在不改变数据模型的情况下,灵活地改变用户界面的展示方式。例如,可以同时显示数据的表格视图和图形视图,或者在打印预览时显示一种特定的视图。 为了实现文档-视图结构,MFC提供了一些基类,如CView和CDocument,以及一些派生类,开发者可以通过继承和扩展这些类来实现自己的文档视图。 在文档-视图结构中,还有一个重要的组件是视图文档框架(View-Document Framework),它负责管理视图文档之间的关系。框架类似于一个协调者,通过注册视图文档的关系,实现视图文档之间的消息传递和同步更新。 总之,MFC文档-视图结构是一种将数据和用户界面分离的设计模式,通过文档管理数据,视图展示数据,并通过框架协调两者之间的关系。这种设计模式使得应用程序更加灵活,易于扩展和维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值