MFC之所以能成为Application Framework, 很大的原因就在于其Document/View结构对于快速开发的支持. Document/View很好地划分了程序代码的前台后台, 让程序员可以专心于设计数据结构和UI.
Document即为“资料”, 按我理解就是饭店的厨师, 而View就是饭店的服务员. View负责点菜和上菜(对用户请求做出直接响应), 而Document负责烹饪, 即处理用户的要求.
除了Document和View, 还有一个Frame, 因为View要放在Frame内部, Frame就是承载View的框架. 而三者之间的关系是由Document Template来管理的, 一份Document Template管理一个Document/Frame/View三件组, 而一个程序可以有多个Document Template, 多个Document Template由一个CDocManager对象管理.
Document Template
一个MDI (多文档接口) 应用程序使用主框架窗口(main frame window)作为工作区, 在工作区里用户可以打开多个文档框架窗口, 每一个文档框架窗口用以显示一份文档.
Document Template是用来定义以下三种类之间关系的模板:
Document (文档)类, 从CDocument派生而来, 用于处理数据, 即所谓数据之体.
View (视图)类, 用于将来自Document类的数据显示出来, 可以从CView, CScrollView, CFormView和CEditView类派生, 也可以直接使用CEditView类.
框架窗口 (Frame Window)类, 用以包含View. 对于MDI程序, 可以从CMDIChildWnd派生, 也可以直接使用该类.
MDI应用程序可以支持不止一种文档,而且不同种类的文档可以同时打开 (比如一个text和一个bitmap). 对于每一种所支持的文档, 应用程序都应该有一份对应的Document template进行管理. 也就是说你的应用程序支持几种文档, 就应该有几个Document Template.
当用户创建新文档的时候, 应用程序就会使用Document Template. 如果程序支持的文档种类在一种以上, 那么程序框架就会从Document Templates处取得所有的文档类型名字, 显示在File New对话框里. 一旦用户选择了文档类型, 应用程序就会创建一个Document对象, 一个Frame Window对象和一个View对象, 并且将它们联系在一起 (通过Document Template).
通常程序员不需要使用CMultiDocTemplate的任何成员函数 (除了构造函数外). 框架会在内部自动处理CMultiDocTemplate对象.
为了管理通过相关View对象和Frame Window对象来构建Document的复杂过程, Framework使用两种Document Template类:
CSingleDocTemplate类用于SDI程序, CMultiDocTemplate类用于MDI程序. 一个CSingleDocTemplate在同一时刻只能创建并储存单一种类的一个文档, 一个CMultiDocTemplate在同一时刻可以管理单一种类的多个文档.
有些应用程序支持不止一种文档类型, 比如同时支持文本和图形. 这种应用程序为每个支持的类型使用单独的Document Template对象, 见下图:
这个应用程序支持两种文档类型,因此具备两个Document Template对象. 对于每一种文档类型可以打开多个文档, 每打开一个文档应用程序就为之创建三个对象: CMyDocument对象用于处理数据, CMyView对象用于显示, CMyFrameWnd用于装载View, 但是不管打开多少个同类型文档, 负责管理该类型的Document Template对象只有一个, 它负责管理的是上述三个类之间的关系, 负责在这三个类的对象创建之时指定它们之间的关系.
上面说到每打开一个Document, 会随之一起创建一个View和一个Frame Window,而这三者的创建工作就是由Document Template完成的, 当用户点击“File/New”或者“File/Open”后, 消息发出, 被theApp的OnFileNew()接到, 但它经过一系列的调用 (比较绕), 最终调用的是CMultiDocTemplate::OpenDocumentFile(), 该函数完成此三对象的创建, 其中View的创建又是非常的绕, 最终经过一系列的调用由CFrameWnd::CreateView()完成, 另外还会调用CView从CWnd继承来的函数Create()用于产生与该View对应的真实窗口. 而创建什么种类的Document, Window, View是在创建Document Template时由Document Template的构造函数的参数指定的. 下面显示了创建一个CMultiDocTemplate (用以管理MDI的document template) 的过程:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_EffectEditorTYPE,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CMyView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
传给构造函数的第一个参数是一个资源ID, 该资源用于提供该文档类型的菜单, 快捷键, 按钮等. 剩余三个参数用RUN_CLASS()宏提供CMultiDocTemplate创建Document/ Window/View时所需要的类型信息 (即对应的RuntimeClass对象, 当用户打开一个文件时, Document Template就可以据此动态创建出Document/Window/View, 这就很好体现了MFC动态创建的用途, 关于动态创建是由DECLARE_DYNCREATE()\IMPLEMENT_DYNCREATE() 宏实现的, 最后用AddDocTemplate()加载此Document Template, AddDocTemplate()实际上是将Document Template加到由theApp的一个指针CDocManager* m_pDocManager所维护的Document Template链表中CDocTemplate有三个成员变量分别持有Document/Window/View的RuntimeClass对象的指针, 另外还有一个资源ID成员.
Document Template对象是被theApp创建的. 在theApp的InitInstance()中的一个关键任务就是创建一个或多个适当种类的Document Template. theApp会在template list中保存指向每一个Document Template的指针并提供一个接口用于增加Document Template (AddDocTemplate()). 如果你想要支持两个或以上的文档类型, 你必须为每个文档类型显式地调用AddDocTemplate().
多个Document Template是由一个CDocManager对象管理的, 很多原本由CWinApp做的关于Document Template的工作如: AddDocTemplate(), OpenDocumentFile(), NewDocumentFile(), 在MFC4.0后都由CDocManager来做了.
CDocTemplate/CDocment/CFrameWnd/CView之间的指针互指关系
CDocTemplate有指向其余三者RuntimeClass对象的指针:
CRuntimeClass* m_pDocClass;
CRuntimeClass* m_pFrameClass;
CRuntimeClass* m_pViewClass;
还有指向Document列表的指针: CPtrList m_pDocList, 表示一个CDocTemplate可以维护多个同类型文档.
CDocument有CDocTemplate* m_pDocTemplate回指CDocTemplate, 另有CPtrList m_pViewList指向一个view的链表, 表示一个Document可以对应多个View.
CFrameWnd有CView* m_pViewActive指向当前活动在其中的View.
CView有CDocument* m_pDocument指向对应的Document.
CDocument/CFrameWnd/CView之间互相操作的函数
CDocument::UpdateAllViews() -> CView:OnUpdate()
CView::GetDocument()
CView::GetParentFrame()
CFrameWnd::GetActiveView()
CFrameWnd::GetActiveDocument()
View和Document的通信
程序员通过改写CMyView的如下函数达到View和Document通信的目的:
CView::OnInitialUpdate(): 负责view的初始化.
CView::OnUpdate(): Frameword在Document发生变化时调用此函数, 此为预留给程序员的 "用Document的变化指导View" 的接口.
CView::OnDraw(): 该函数作为WM_PAINT的间接响应, 负责View的更新.
CDocument::UpdateAllViews()/CView::OnUpdate()这一对函数是命令与执行的关系, 调用UpdateAllViews()就会通知所有的View, 通知方法就是调用其OnUpdate().