第篇 深入 MFC 程序设计
第8章 Document-View 深入探讨
Document-View 深入探讨
形而者谓之道,形而者谓之器。
对于 Document/View 而言,很少有能够先道而后器。
完全由 AppWziard 代劳做出的 Scribble step0,应用程序的整个架构(空壳)都已经建
构起来了,但是 Document 和 View 还空着好几个最重要的函式(都是虚拟函式)等着
你设计其实体。这就像部汽车外面的车体以及内部的油路电路都装配好了,但还等着
最重要的发动机(引擎)植入,才能够产生动力,开始「有所为」。
我已经在第7章概略介绍了 Document/View 以及 Document Template,还有更多的秘密将
在本章揭露。
为什么需要 Document-View(形而上)
MFC 之所以为 Application Framework,最重要的个特征就是它能够将管理数据的程序
码和负责数据显示的程序代码分离开来,这种能力由 MFC 的 Document/View 提供。
Document/View 是 MFC 的基石,了解它,对于有效运用 MFC 有极关键的影响。甚至
OLE 复合文件(compound document)都是建筑在 Document/View 的基础呢! 几乎每个软件都致力于数据的处理,毕竟信息以及数据的管理是计算机技术的主要用途。
把数据管理和显示方法分离开来,需要考虑列几个议题: 455
第篇 深入 MFC 程序设计
1. 程序的哪部份拥有数据
2. 程序的哪部份负责更新数据
3. 如何以多种方式显示数据
4. 如何让数据的更改有致性
5. 如何储存数据(放到永久储存装置)
6. 如何管理使用者接口。不同的数据型态可能需要不同的使用者接口,而个程
式可能管理多种型态的数据。
其实 Document / View 不是什么新主意,Xerox PARC 实验室是这种观念的滥觞。它是
Smalltalk 环境的关键性部份,在那里它被称为 Model-View-Controller(MVC)。其
的 Model 就是 MFC 的 Document,而 Controller 相当于 MFC 的 Document Template。
回想在没有 Application Framework 帮助的时代(并不太久以前),你如何管理数据?只
要程序需要,你就必须想出各种表现数据的方法;你有责任把数据的各种表现方法和资
料本体调解出种关系出来。100 位程序员,有 100 种作法!如果你的程序只处理种
数据型态,情况还不至于太糟。举个例,字处理软件可以使用巨大的字符串数组,把文
字统统含括进来,并以 ASCII 型式显示之,顶多嘛,变换字形!
但如果你必须维护种以的数据型态,情况又当如何?想象得到,每种数据型态可
能需要独特的处理方式,于是需要套功能选单;每种数据型态显现在窗口,应该
有独特的窗口标题以及缩小图标;当数据编辑完毕要存盘,应该有独特的扩展名;登录
在 Registry 之应该有独特的型号。再者,如果你以不同的窗口,不同的显现方式,秀
出份数据,当数据在某窗口被编辑,你应该让每窗口的数据显像与实际数据之
间常保致。吧啦吧啦吧啦 K ,繁杂事务不胜枚举。
很快,问题就浮显出来了。程序不仅要做数据管理,更要做「与数据型态相对应的 UI」
的管理。幸运的是,解决之道亦已浮现,那就是对象导向观念的 Model-View-Controller
(MVC),也就是 MFC 的 Document / View。
456
第8章 Document-View 深入探讨
Document
名称有点令惧怕 -- Document令我们想起文字处理软件或电子表格软件所谓的「文件」。
但,这里的 Document 其实就是数据。的确是,不必想得过份复杂。有用 data set 或 data
source 来表示它的意义,都不错。
Document 在 MFC 的 CDocument 里头被具体化。CDocument 本身并无实务贡献,它
只是提供个空壳。当你开发自己的程序,应该从 CDocument 衍生出个属于自己的
Document 类别,并且在类别宣告些成员变量,用以承载(容纳)数据。然后再(至
少)改写专门负责档案读写动作的 Serialize 函式。事实,AppWizard 为我们把空壳都
准备好了,以是 Scribble step0 的部份内容:
class CScribbleDoc : public CDocument
{
DECLARE_DYNCREATE(CScribbleDoc)
...
virtual void Serialize(CArchive& ar);
DECLARE_MESSAGE_MAP()
};
void CScribbleDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}
由于 CDocument 衍生自 CObject,所以它就有了 CObject 所支持的切性质,包括执行
时期型别信息(RTTI)、动态生成(Dynamic Creation)、档案读写(Serialization)。又
由于它也衍生自 CCmdTarget,所以它可以接收来自选单或工具列的 WM_COMMAND 讯
息。
457
第篇 深入 MFC 程序设计
View
View 负责描述(呈现)Document 的数据。
View 在 MFC 的 CView 里头被具体化。CView 本身亦无实务贡献,它只是提供个空
壳。当你开发自己的程序,应该从 CView 衍生出个属于自己的 View 类别,并且在
类别(至少)改写专门负责显示数据的 OnDraw 函式(针对屏幕)或 OnPrint 函式
(针对打印机)。事实,AppWizard 为我们把空壳都准备好了,以是 Scribble step0 的
部份内容:
class CScribbleView : public CView
{
DECLARE_DYNCREATE(CScribbleView)
...
virtual void OnDraw(CDC* pDC);
DECLARE_MESSAGE_MAP()
};
void CScribbleView::OnDraw(CDC* pDC)
{
CScribbleDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
由于 CView 衍生自 CWnd,所以它可以接收般 Windows 讯息(如 WM_SIZE、
WM_PAINT 等等),又由于它也衍生自 CCmdTarget,所以它可以接收来自选单或工具
列的 WM_COMMAND 讯息。
在传统的 C/SDK 程序,当窗口函式收到 WM_PAINT,我们(程序员)就呼叫
BeginPaint,获得个 Device Context(DC),然后在这个 DC 作画。这个 DC 代表萤
幕装置。在 MFC 里头,旦 WM_PAINT 发生,Framework 会自动呼叫 OnDraw 函式。
View 事实是个没有边框的窗口。真正出现时,其外围还有个有边框的窗口,我们称
为 Frame 窗口。
458
第8章 Document-View 深入探讨
Document Frame(View Frame)
如果你的程序管理两种不同型态的数据,譬如说个是 TEXT,个是 BITMAP,作为
位体贴的程序设计者,我想你很愿意为你的使用者考虑多些:你可能愿意在使用者
操作 TEXT 数据时,换套 TEXT 专属的使用者接口,在使用者操作 BITMAP 数据时,
换套 BITMAP 专属的使用者接口。这份工作正是由 Frame 窗口负责。
乍见这个观念,我想你会惊讶为什么 UI 的管理不由 View 直接负责,却要交给 Frame
窗口?你知道,有时候机能与机能之间要有点黏又不太黏才好,把 UI 管理机能隔离出来,
可以降低彼此之间的依存性,也可以使机能重复使用于各种场合如 SDI、MDI、OLE in-place
editing(即编辑)之。如此来 View 的弹性也会大些。
Document Template
MFC 把 Document/View/Frame 视为位体。可不是吗!每当使用者欲打开(或新增)
份文件,程序应该做出 Document、View、Frame 各份。这个「口组」成为个
运作单元,由所谓的 Document Template 掌管。MFC 有个 CDocTemplate 负责此事。
它又有两个衍生类别,分别是 CMultiDocTemplate 和 CSingleDocTemplate。
所以我在章说了,如果你的程序能够处理两种数据型态,你必须制造两个 Document
Template 出来,并使用 AddDocTemplate 函式将它们加入系统之。这和程序是不
是 MDI 并没有关系。如果你的程序支持多种数据型态,但却是个 SDI,那只不过表示
你每次只能开启份文件罢了。
但是,逐渐,MDI 这个字眼与它原来的意义有了些出入(要知道,这个字眼早在 SDK
时代即有了)。因此,你可能会看到有些书籍这么说:『MDI 程序使用 CMultiDocTemplate,
SDI 程序使用 CSingleDocTemplate』,那并不是很精准。
CDocTemplate 是个抽象类别,定义了些用来处理「Document/View/Frame 口组」的
基础函式。
459
第篇 深入 MFC 程序设计
CDocTemplate 管理 CDocument / CView / CFrameWnd
好,我们说 Document Template 管理「口组」,谁又来管理 Document Template 呢?
答案是 CWinApp。面就是 InitInstance 应有的相关作为:
BOOL CScribbleApp::InitInstance()
{
...
CMultiDocTemplate* pDocTemplate;