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_CMyDocTypeTYPE,
?? 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()。