MFC应用程序框架-文档/视结构

@[MFC应用程序框架-文档/视结构]

MFC使用文档/视(Document/View)结构协实现了数据管理和数据显示的分离。

因为利用MFC文档/视结构编写应用程序时,

应用程序向导自动产生的代码将为应用程序完成很多常规的操作,

因此

程序员可以省去很多重复劳动,把精力集中在应用程序的主体,也就是应用程序数据的处理上。

用文档/视结构编写应用程序,

最基本的是要知道编译器已经完成了哪些操作,还需要添加哪些操作,需要修改哪些操作;

更高的要求就是要搞清楚编译器是如何实现这些操作的,以便可以修改相应的地方以实现特定的功能。

1.MFC单文档应用程序结构

利用AppWizard自动生成文档/视结构的单文档项目Test,

Test至少应由CTestApp、 CTestDoc、CtestView、 CmainFrame和CAboutDlg几个部分组成,

下图5.1是其类结构图。 图5.1 向导产生的Test项目的类

图中虚框内是向导以项目名字Test为依据,为应用程序派生的类。

CAboutDlg提供一个文本编辑框,可以填写信息或者直接采用。

它也产生了基本的菜单和工具栏内容,具有默认的状态栏,这里均不涉及。

下面简要说明文档/视结构派生类的意义和作用。

① CTestDoc类对应文档,文档可以理解为应用程序数据的抽象集合,它负责应用程序数据的存储,包括和磁盘文件的数据交换。

② CTestView类对应视,它是应用程序数据在用户面前的表达,负责应用程序数据的显示以及处理用户对数据的操作。

③ CMainFrame类对应窗口, 它是视的容器,在高级应用程序中它还容纳了工具栏、状态栏等。

④ CTestApp类由CWinApp派生,它对应对象并充当消息传递中心和全部应用程序对象的容器。CWinApp对象还拥有并控制文档模板,这就是如图5.1中的CSingleDocTemplate 和CDocTemplate。文档模板产生文档、框架窗口及视。各对象的作用示意图见图5.2。 在这里插入图片描述 图5.2 各对象的作用示意图

各个对象都是从相应的基类派生而来的,而且它们相互之间也存在一定的联系,具体如表5.1所示。

由此可见,

Document/View 是MFC 的基石,其最重要的一个特征就是它能够将管理数据的程序代码和负责数据显示的程序代码分离开来。

要把数据管理和显示方法分离开来,

需要考虑程序的哪一部分拥有数据和负责更新数据及显示数据;如何能使数据的更改具有一致性;如何将数据存到永久存储装置上;如何管理使用者接口。尤其是呈现不同形态的数据可能需要不同的使用者接口,而一个程序可能要管理多种形态的数据。

表5.1 对象的基类及其相互联系 在这里插入图片描述

2.文档对象

MFC中的Document就是数据,它在CDocument 里面被实例化。

CDocument 本身只是提供一个框架。

当开发自己的应用程序时,应用它派生出自己的Document 类,并在类中声明一些数据成员,用来接纳数据。

需要时还得改写专门负责档案读写动作的Serialize 函数。

事实上, AppWizard已经把程序的框架都准备好了,在指定位置添加代码即可。CDocument对象提供视对象显示的信息,但它总是驻留在应用程序界面后面,相当于View用来显示的容器。

2.1基类CDocument

CDocument类在MFC中的层次结构的位置, 如图6.3所示。 CDocument对象提供视对象显示的信息,但它总是驻留在应用程序界面后面,相当于View用来显示的容器。

CDocument 类为文档建立及存档提供支持并提供应用程序用于控制其数据的接口。

由于CDocument派生自CObject, 所以它就有了CObject所支持的一切性质, 包括RTTI、 Dynamic Creation和Serialization。

CDocument 也派生自CCmdTarget, 所以它可接收来自菜单或工具栏的WM_COMMAND 消息。 在这里插入图片描述 图6.3 CDocument在层次结构中的位置 CDocument 类有很多有用的成员函数。

(1) Serialize()函数

函数原型: virtual void Serialize(CArchive& ar);

函数作用: 通过CArchive类实现应用程序和文件的数据交换。也称序列化。

函数的一般格式如下:

void CMyDoc∷ Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}else
{
// TODO: add loading code here
}
}
复制代码

虽然不可能预先处理未知的资料,但却可以处理内嵌对象。

因为这些对象能用来处理诸如数组等基层资料,所以只要CDocument 把框架搭好,程序员就可以在 Document中拼拼凑凑出实际想要表达的文件的完整格式。

(2) OnNewDocument()函数

函数原型: virtual BOOL OnNewDocument();

函数作用: 调用该函数以新建文档,该函数默认调用DeleteContents()成员函数,以便清除当前文档对象中的所有数据。

在SDI应用程序中,用户选择“新建”命令后,这个函数将重新初始化已经存在的文档,而不是重新再创建一个新的文档。 在MDI应用程序中,将创建一个新的文档并调用这个函数初始化该文档。

(3) OnOpenDocument()函数

函数原型: virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);

函数作用: 调用这个成员函数以打开一个特定的文件。它首先调用DeleteContents()成员函数以清除 文档,然后调用Serialize()成员函数以读取文件的内容。

如果在SDI应用程序中,选择“打开”命令时,应用程序会调用这个函数初始化已经存在的文档,而不是创建一个新对象。在MDI中,应用程序将创建一个新的文档对象,然后用从文件中取得的数据初始化它。

(4) DeleteContents()函数

函数原型: virtual void DeleteContents();

函数作用: 初始化文档对象,清除其中的所有数据。

(5) SetModifiedFlag()函数

函数原型: virtual void SetModifiedFlag(BOOLbModified = TRUE);

函数作用: 设置文档修改标志,如果标志为真,则退出该文档的时候,应用程序会提示是否保存到 文档。

2.2在应用程序中使用文档类的典型步骤

① 以CDocument为基类派生出自己的文档类。

② 将文档中需要添加处理的数据作为数据成员。如果这些数据成员要提供给View使用,一般直接设计为公有的。如果设计为私有的,则必须为它们设计公有函数,以便通过公有函数提交给View使用。设计的公有数据成员在这个文件内失去了封装性,但数量不多,是可以接受的。

③ 如果需要, 编写用于读取和修改文档数据的成员函数。

④ 重载或修改用于文件操作的函数。

3.视的对象

视的对象是物理上的应用客户区域, 逻辑上的文档类中信息的显示方式,并允许用户使用键盘和鼠标进 行输入。

一个视只能和一个文档相联系,而一个文档可以拥有不止一个视。视显示的可以是纯文本、 图形或者图 文混合物。

View事实上是个没有边框的窗口。真正出现时,其外围还有一个有边框的窗口, 称为Frame窗口。 鉴于 此种原因,不称它为“视窗”或“视图”,而称为视。

在单文档中, 视属于框架窗口并完全覆盖用户区。在一个正在运行的单文档应用程序中,视和框架的关 系如图5.4所示。

在这里插入图片描述 图5.4 视和框架的位置示意图

View 在MFC 的CView 里面被实例化。

与CDocument 一样, CView 本身亦无实际用途,它只是提供一个空壳。

当开发应用程序时,应该从CView 派生出一个属于自己的View类,并且在类中(至少)改写专门负责显示各种信息的OnDraw函数(针对屏幕)或OnPrint 函数(针对打印机)。

CView 派生自CWnd, 所以可接收一般的Windows消息(如WM_SIZE消息和WM_PAINT消息等),又由于它也派生自CCmdTarget, 所以也可接收来自菜单或工具栏的WM_COMMAND 消息。

在传统的C/SDK 程序中,当窗口函数收到WM_PAINT时,需要使用BeginPaint获得一个设备句柄DC,然后在这个DC上作画。这个DC代表屏幕。

在MFC 中,一旦发生WM_PAINT 消息,Framework 会自动调用OnDraw 函数。

3.1基类CView

应用程序中的视总是由CView或者它的子类派生。

CView 类在MFC层次结构中的位置如图5.5所示。

在这里插入图片描述 图5.5 CView在MFC层次结构中的位置

下面简要介绍CView类一些重要的成员函数。

(1) GetDocument()函数

函数原型: CDocument* GetDocument() const;

函数作用: 获得文档指针。

(2) OnInitialUpdate()函数

函数原型: virtual void OnInitialUpdate();

函数作用: 在视第一次和文档相连的时候调用。在基类的OnInitialUpdate函数中,只调用OnUpdate函数,就可以完成视的初始化。应用程序框架在创建视时,先调用OnCreate函数,然后再调用这个函数。 OnCreate只能调用一次,而这个函数可以调用许多次。

( 3) OnUpdate()函数

函数原型: virtual void OnUpdate(CView* pSender,LPARAM lHint, CObject* pHint);

函数作用: 访问文档数据,根据数据更新视的数据成员和控件,以反映文档的变化。默认的情况下,它使整个视无效,同时触发OnDraw函数,使用更新后的文档数据重新绘制窗口。它可以被文档类的UpdateAllViews调用,也可以在视类中由用户调用。

( 4) OnDraw()函数

函数原型: virtual void OnDraw(CDC* pDC) = 0;

函数作用: 通过调用文档数据操作的成员函数获得文档数据,并将其传递给设备环境对象的成员函数,以显示文档数据。

3.2常用的CView派生类

除了CView类之外,还有很多它的派生类也可以作为视对象的基类,这些派生类都在CView类上加了特定的功能,因此可以用做特殊用途。

表5.2给出常用的CView 派生类。

表5.2 常用的CView 派生类 在这里插入图片描述

3.3在应用程序中使用视类的典型步骤

① 以CView为基类派生出自己的视类;

② 改写OnDraw 虚函数;

③ pDC指针是虚函数已经声明的参数,可以直接使用;

④ View已经在OnDraw函数中声明了文档指针并将它初始化指向文档, 视与文档的数据交换通过这个指针实现,

例如在项目Test的OnDraw函数中,使用如下语句实现:

CTestDoc* pDoc = GetDocument();
复制代码

⑤ 如果需要, 增加合适的成员函数。

4.文档和视的联系

不论是什么类型的数据, 数据总是有体有面的。实际数据的数值是体,则显示在屏幕上(或者打印出来)的画面就是面。

数值的处理应该使用字节、整数、浮点数、列表、数组等数据结构,而数值的表现应该使用绘图方式, 例如坐标系统、笔刷颜色、点、线、圆弧、字型……。

CView 就是为了数据的表现而设计的。

Document/View 的价值在于,这些MFC类已经把一个应用程序所需的“数据处理与显示”的函数框 架都设计好了,这些函数都是虚函数,可以在派生类中改写它们。

有关档案读写的动作在CDocument 的Serialize 函数里进行,有关画面显示的动作在CView的OnDraw 或OnPaint 函数里进行。

当为自己派生CTestDoc和CtestView两个类时,只要把全部心思花在CTestDoc∷ Serialize 和CTestView∷ OnDraw上, MFC已经把程序大架构完成了,模块与模块间的信息流动路径以及各函数的功能和任务都已确定好(这是MFC 之所以够格称为一个Framework 的原因),所以写程序的焦点就放在那些必须改写的虚函数上即可。软件界当初发展GUI 系统时,目的也是希望引导程序员把主要的精力放在应用软件的真正目标上,而不必花在使用者的接口上。

MFC 的Document/View结构就是想把程序员的心力导引到真正的“数据结构设计”以及真正的“数据显示动作”上,而不要花在模块的沟通或信息的流动传递上。

今天,程序员都对GUI 称便,这也证明了Document/View的贡献。 Application Framework使程序写作犹如做填充题; Visual C ++6.0的软件开发工具则使编程犹如做选择题。先根据需要做选择题,系统给出完成此设想的参考填充题,再在程序框架中做填充题。

4.1逻辑关系

图5.6是CDocument /CView的逻辑关系示意图。现解释如下:

① CDocument容纳应用程序的数据, CView负责把数据显示出来,并接受用户的修改,然后CView通知CDocument数据已经更改, CDocument将该更改保存到文件中。

② 当打开或新建CDocument时, CDocument根据文件的内容将数据初始化,然后通知CView更改显示。 在这里插入图片描述 图5.6 CDocument /CView逻辑关系示意图

4.2调用关系

文档和视的联系主要是通过成员函数的调用来实现的。

4.2.1从文档中获得视

① 文档类中含有所有相关视的列表,可以通过以下函数来访问该列表:

CDocument∷ GetFirstViewPosition()
CDocument∷ GetNextView()
复制代码

它们的函数原型分别为:

virtual POSITION GetFirstViewPosition() const;
virtual CView* GetNextView(POSITION& rPosition) const;
复制代码

② CDocument 还提供UpdateAllViews成员函数,它通过遍历所有与本文档相关的视并调用

CView∷ OnUpdate()
复制代码

成员函数来通知它们重绘自己。UpdateAllViews函数原型为:

void UpdateAllViews(CView* pSender,LPARAM lHint = 0L,CObject* pHint = NULL);
复制代码

其中pSender为发送更改通知给文档的视对象的指针,这个视当然不用更改。 pHint可以用来设置无效区域,以提高重绘效率。

4.2.2从视中获得文档

CView类提供一个GetDocument()成员函数来获取与之相关的文档的指针。

通过这个文档指针可以存取文档中的数据。

5.框架窗口

对于同一份文档,它既可以映像给许多个视显示,不同的视也可以对应它的不同区域。

总之,

可以把视想象成一个镜头,虽然被观察的对象完全相同,使用滚动条可以调节观看大画布的不同区域;在镜头上加特殊的偏光镜、柔光镜、十字镜,就会看到不同的影像。

换言之,

MFC中的一体可以多面,即同一份数据可用文字、直方图或者其他曲线图描述。

使用者透过View既可看到Document, 也能改变Document。

View是Document的外显接口,但它并不能完全独立,它必须依存在一个所谓的Document Frame 窗口内。

暂且称为程序的框架窗口,其实它就是程序的主窗口。

在单文档应用程序中,框架窗口既是主框架窗口又是文档框架窗口, 基类为CFrameWnd类。

框架窗口对象在应用程序运行时创建,应用程序结束时销毁。在AppWizard 生成的单文档应用程序中,框架窗口类为CMainFrame。

5.1基类 CFrameWnd

CFrameWnd 类在MFC层次结构中的位置如图5.7所示。 在这里插入图片描述 图5.7 CFrameWnd 类在MFC层次结构中的位置示意图

•CFrameWnd类中含有很多有用的成员函数,详见表5.3。

表5.3 CFrameWnd类中重要的成员函数及其作用 在这里插入图片描述

5.2框架窗口的构造方法

可用以下3种方法之一来创建框架窗口:

① 调用CFrameWnd∷ Create()函数直接构造;

② 调用CFrameWnd∷ LoadFrame()函数直接构造;

③ 使用文档模板来构造它。

MFC从类CFrameWnd中派生出框架窗口对象,然后调用对象的LoadFrame函数来创建实际的窗口,该框架窗口的建立是基于从资源文件中加载的信息,MFC将该窗口的句柄连接到应用程序的框架窗口对象(即连接到对象的数据成员中)。

下面是一个典型过程:

CFrameWnd∷ LoadFrame();
CFrameWnd∷ Create();
CFrameWnd∷ CreateEx();
∷ CreateWindowsEX();
复制代码

触发WM_CREATE进入MainFrm.cpp的

消息映射宏


BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()
CMainFrame∷ OnCreate();
复制代码

如果操作文本数据时使用文本专用的接口,在操作位图数据时,需要换一套位图专用的接口。这份工 作正是由Frame 窗口负责。

UI 的管理不由View直接负责,而是交给Frame窗口,这就把UI 管理机能隔离出来,从而降低彼此之间的依存性,也可以使这些功能重复使用于各种场合,如SDI、 MDI、 OLE in-place editing(即地编辑)之中。这样就可以使View 的弹性大些。

6.文档模板

文档模板(Document Template)主要用于建立和管理文档、视和框架对象。

如前所述, View 的外围必须再包装一个外框窗口作为舞台。

这样做的目的其实是为了让View 可以独立地放置于MDI窗口或SDI窗口(或OLEDocument Frame 窗口)等各种应用之中。

Document Frame 窗口是View 窗口的一个容器。

数据的内容、表现以及容纳数据表现的外框窗口三者是一体的。

对于多文档来说,程序每打开一份文件(数据),就应该产生3份对象:

Document对象、

View对象

和CMDIChildWnd 对象(作为外框窗口)。

这3份对象由一个所谓的Document Template 对象来管理。

让这3份对象产生关系的关键在于CMultiDocTemplate如果程序支持不同的数据格式(例如一个为TEXT一个为BITMAP), 那么就需要不同的DocumentTemplate。

对于单文档而言,并不是说它只能是一种数据类型的文档,而是说一旦在选择框架时选定了数据类型的种类,它就只能是这种类型的文档了。

6.1基类CDocTemplate

所有的文档模板都是从CDocTemplate类派生来的。

在单文档应用程序中,文档模板是从CSingleDocTemplate类派生而来的。

多文档应用程序是从CMultiDocTemplate类派生而来的。

它们在MFC层次结构中的位置如图5.8所示。

在这里插入图片描述 图5.8 文档模板在MFC 层次结构中的位置示意图

CDocTemplate是抽象类,它定义了一些用来处理Document/View/Frame 三位一体的基础成员函数。

常用的成员函数如下:

(1) CreateNewFrame()函数

函数原型: CFrameWnd* CDocTemplate∷CreateNewFrame(CDocument *pDoc,CFrameWnd *pFrmWnd);

参数说明: pDoc是一个文档对象,如果是新建的文档,就是一个空文档,如果是一个已存在的文档建立新的View, 则只需获得这一文档对象的指针。pFrmWnd是框架窗口,对于多文档结构,就是一个CMDIChildWnd的对象指针

返回值: 若成功,返回一个主框架窗口的指针,否则返回一个NULL。

( 2) InitialUpdateFrame() 函数原型: InitialUpdateFrame(CFrameWndpFrmWnd,CDocument pDoc);

参数说明: pFrmWnd是主框架窗口指针,单文档的pDoc是新建文档对象的指针。

6.2文档模板的创建

在创建文档模板的过程中,文档模板取指向CRuntimeClass对象的指针作为标识文档、视、框架对象的参数。

CDocTemplate∷ CDocTemplate()构造函数原型如下:

CDocTemplate ( UINT nIDResource,
CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass,
CRuntimeClass* pViewClass );
复制代码

参 数 说 明 nIDResourc 和文档资源一起使用的各种资源的资源标识符

pDocClass 指向CDocument 派生类的

CRuntimeClass 对象的指针

pFrameClass 指向CFrameWnd 派生类的

CRuntimeClass 对象的指针

pViewClass 指向CView 派生类的

CRuntimeClass 对象的指针

文档模板由CWinApp管理,由CTestApp创建和维护。在CTestApp∷ InitInstance()函数中创建,然后使用CWinApp∷ AddDocTemplate()函数将模板加到应用中去。

下面是示例代码:

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CTest21Doc),RUNTIME_CLASS(CMainFrame),RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
复制代码

6.3Decument Template 产生三位一体的流程

单文档有一个数据成员CDocument* m_pOnlyDoc它一次只能打开一个文档。

图5.9是执行CTestApp∷ InitInstance()创建三位一体的流程图。 在这里插入图片描述 图5.9 动态创建及三位一体流程示意图

View窗口就是众所周知的Windows窗口, MFC为了对象管理而把View窗口外包装一个专门的C ++类,即CView类。

必须先构造(construct)一个View窗口对象,才能由View窗口对象的构造函数产生(create)对应的View窗口。

Frame窗口对象和Frame窗口的关系也是如此。

【例5.1】 使用文本文档的例子

可以使用向导建立一个简单的记事本程序。

如图5.10所示,在第6步时,打开Base class的下拉列表框,将CTextView的基类改为CEditView。

注意: CEditView是CView的派生类。 在这里插入图片描述 图5.10 将CView改为CEditView示意图

继续按向导的提示完成程序框架,就可以得到一个类似记事本功能的文字编辑器。

运行时是“无标题”作为文件名,图5.11是将它存为ask后的示意图

在这里插入图片描述

图5.11 程序运行示意图 文件名可以不用后缀,也可以用txt作后缀。图中工具栏和菜单均可以使用,但一次只能打开一个文档。

程序每次运行自动产生一个无标题的空文档,可以在这个文档里编辑新文档,然后存入文件。

也可取入已经存在的文档再次进行编辑。

文件存取和显示均由向导自动实现,程序员完全不要添加代码。

下面给出自动生成的Serialize和OnDraw函数的代码。

// CTextDoc.cpp
void CTextDoc∷ Serialize(CArchive& ar)
{
// CEditView contains an edit control
// which handles all serialization
    ((CEditView*)m_viewList.
    GetHead())->SerializeRaw(ar);
}

//CTextView.cpp
void CTextView∷ OnDraw(CDC* pDC)
{
    CTextDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
复制代码

7.分析单文档应用程序

通过以上的介绍,已经对文档/视结构的应用程序的各个对象有了一个初步的了解,

利用以上的知识,已经可以创建一个简单的单文档应用程序了。

在建立一个有用的应用程序之前,

先来看看AppWizard 产生的空的Test单文档应用程序中,编译器都做了什么。

AppWizard 为Test产生了5个类,展开后的情形如图5.12所示。

由于图形太长,所以只保留部分需要添加代码的成员函数。

下面就根据这张图,对各个对象进行分析,看看编译器为用户做了哪些有意义的工作,自己还要注意改写哪些函数。

在这里插入图片描述 图5.12 展开后的ClassView

应用程序类CTestApp

CTestApp类惟一实际的活动就是建立InitInstance函数。

AppWizard向导帮助建立的函数中,包括了如下几个部分:

① 创建单文档模板对象,并将它添加到应用程序的文档模板表中:

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CTestDoc),RUNTIME_CLASS(CMainFrame),
CTestAppRUNTIME_CLASS(CTestView));

AddDocTemplate(pDocTemplate);
复制代码

② 创建CCommandLineInfo对象并将其传递给CWinApp∷ ParseCommandLine()函数:

// Parse command line for standard shell
// commands, DDE, file open

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

复制代码

CWinApp∷ ParseCommandLine()函数原型为:

void CWinApp∷ ParseCommandLine(CCommandLineInfo& rCmdInfo ) ;
复制代码

其中rCmdInfo 对象包含由用户规定的任何命令行参数信息。

这个函数为命令行的每一个参数调用CCommandLineInfo∷ ParseParam()函数,并填充CCommandLineInfo对象。

③ 显示主窗口:

// The one and only window has been
//initialized, so show and update it.

m_pMainWnd->ShowWindow(SW_SHOW);

m_pMainWnd->UpdateWindow();
复制代码

向导已经自动将这个类的内容填写完毕。

一对“{{AFX”和“//}}AFX”符号之间的内容是系统使用的,不要去修改它。

一般来讲, 可以修改的地方都有提示。

文档类 CTestDoc

CTestDoc中实际有用的有两个函数,即

OnNewDocument()

Serialize(CArchive& ar)

OnNewDocument()的实现代码如下:

BOOL CTestDoc∷ OnNewDocument()
{
   if (!CDocument∷ OnNewDocument())
       return FALSE;
   return TRUE;
}
复制代码

可见,它除了调用直接基类的CDocument∷ OnNewDocument()之外什么也没做。

添加打开、新建文档功能时需要对这里进行修改。

Serialize(CArchive& ar)的实现代码如下:

void CTestDoc∷ Serialize(CArchive& ar)
{
    if (ar.IsStoring())
       {// TODO: add storing code here}
    else
       {// TODO: add loading code here}
}
复制代码

除了给出一个完整的序列化框架之外,它什么也没做,

在一个有实际功能的应用程序中如果要存取文件,通常就是修改这里的序列化函数。

视类 CTestView

在CTestView 类中,经常用到的函数是OnDraw和GetDocument。

GetDocument直接拿来用即可。

OnDraw函数需要根据要添加的功能进行修改。

至于打印相关的函数,由于MFC框架默认的操作一般可以达到要求,所以不用修改。

框架窗口类 CMainFrame

这个类的成员函数所做的事情就是在创建时调入各种资源。

在不拆分视的单文档应用程序中,这个类不用修改。

对话框类CAboutDlg

这是产生About对话框的类,默认的如图5.13所示。

如果需要修改,使用资源浏览器展开资源,鼠标单击图中的IDD_ABOUTBOX, 直接在右边的界面上修改即可。

在这里插入图片描述 图5.13 对话框资源示意图

向导在Test.cpp文件里以CDialog为基类自动派生一个CAboutDlg类,声明消息映射并给出实现宏。

当然,这些都是框架,需要时自己再在相应框架中填加代码。

class CAboutDlg : public CDialog
{
public:
   CAboutDlg();
// Dialog Data
//{{AFX_DATA(CAboutDlg)
   enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)protected:
   virtual void DoDataExchange(CDataExchange*pDX);// DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
// No message handlers
//}}AFX_MSG
   DECLARE_MESSAGE_MAP()
};
   BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers//}}AFX_MSG_MAP
   END_MESSAGE_MAP()
复制代码

对话框是通过“帮助”菜单的“关于Test…”命令弹出的,收起则靠对话框中的“确定”按钮。

单击这个按钮,调用OnAppAbout函数处理。

系统自动将它作为CTestApp类的一个成员函数,并为它定义如下:

void CTestApp∷ OnAppAbout()
{
   CAboutDlg aboutDlg;
   aboutDlg.DoModal();
}
复制代码

工具栏和状态栏

工具栏和状态栏分别由CToolBar和CStatusBar管理。

两个对象作为CMainFrame类的数据成员,在MainFrm.h中声明CMainFrame类时声明它们。

class CMainFrame : public CFrameWnd
{
protected:// control bar embedded members
    CStatusBar m_wndStatusBar;
    CToolBarm_wndToolBar;
…
};
复制代码

主窗口产生之际立刻会发出WM_CREATE消息,应该利用这时机把工具栏和状态栏建立起来。

为了拦截WM_CREATE消息,首先需在Message Map中设定映射项,这在MainFrm.cpp中实现如下:

BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)

ON_WM_CREATE()

END_MESSAGE_MAP()
复制代码

ON_WM_CREATE 宏表示只要WM_CREATE 发生,就调用OnCreate 函数。

下面是由AppWizard 产生的OnCreate 标准动作:

int CMainFrame∷ OnCreate(LPCREATESTRUCTlpCreateStruct)
{
    if (CFrameWnd∷ OnCreate(lpCreateStruct) == -1)
    return -1;
    if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,WS_CHILD | WS_VISIBLE |CBRS_TOP|CBRS_GRIPPER|CBRS_TOOLTIPS|CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
         {
              TRACE0(″Failed to create toolbar\n″);
              return -1;// fail to create
         }
     if (!m_wndStatusBar.Create(this) ||!m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT))) 
     {
            TRACE0(″Failed to create status bar\n″);
            return -1;// fail to create
     }
// TODO: Delete these three lines if you don′t
//want the toolbar to be dockable
    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    EnableDocking(CBRS_ALIGN_ANY);
    DockControlBar(&m_wndToolBar);
    return 0;
}
复制代码

第2个if语句是判断产生工具栏是否成功,

其中语句m_wndToolBar.LoadToolBar(IDR_MAINFRAME) //载入工具栏是装入资源RC中的工具栏资源。

参数IDR_MAINFRAME在RC资源中代表与工具栏有关的资源(参见图5.14)。

wndStatusBar.Create(this) 表示要产生一个隶属于this对象的主窗口的状态栏。

m_wndStatusBar.SetIndicators中的第1个参数是个数组;

第2个参数是数组元素个数。所谓indicator是状态栏最右侧的指示窗口,用来表示大写键、数字键等的On/Off 状态。

在文件AFXRES.H 中定义有7种indicators, 这里使用了其中3种,它们在MainFrm.cpp中定义成静态数组,如下所示:

static UINT indicators[ ] =
{
   ID_SEPARATOR, // status line indicator
   ID_INDICATOR_CAPS,
   ID_INDICATOR_NUM,
   ID_INDICATOR_SCRL,
};
复制代码

标准菜单

程序已经具有“文件”、“编辑”、“查看”和“帮助”等菜单,每个菜单也设置了相应命令。

如图5.14所示,使用“资源浏览”按钮转到资源浏览窗口,双击资源名称即可在右边修改相应资源。

凡是能弹出对话框的菜单命令, 其相应的菜单命令也都设置完毕(例如文件菜单能弹出Open 对话框、Save As 对话框、 Print 对话框、 Print Setup 对话框等等)。

编辑(Edit) 菜单上的每一项功能都已经可以应用在使用CEditView 派生的文字编辑器上。

文件(File)菜单最下方记录着最近使用过的(所谓LRU) 4个文件名称(个数可在AppWizard 中更改),以方便重新打开。

查看(View)菜单允许将工具栏和状态栏设为可见或隐藏。帮助菜单含有About命令。

多文档的菜单更为复杂。

从图5.14可见,单文档还有Icon和String Table等资源,这些就不一一介绍了。初学时可以不去研究它们。

在这里插入图片描述 图51.14 资源浏览和菜单资源

8.小结

MFC的文档/视结构提供了一种将应用程序数据存储和显示分离的方式来组织应用程序,

在这个结构中, 文档主要负责数据的存储,而视主要负责数据的显示及用户交互,

这使得数据的显示和数据的存储分离,

因此

在文档/视结构的应用程序中,一份数据可以由不同的视以不同的方式显示而不必考虑数据的存储方法。

同样的道理,数据可以以任意用户希望的形式存储,这样就给编写程序带来很多的方便。

MFC应用程序框架还提供各种通用的操作, 避免程序员进行不必要的重复劳动。

利用MFC的文档/视结构编写程序,

不仅要了解必要的类及其成员函数,

更要理解各对象之间的关系,

还要了解编译器做了哪些工作, 自己要做哪些工作,

更高的要求是了解编译器是如何完成它所做的工作的,有哪些地方可以让用户修改, 如何修改,以及编译器希望用户用什么方式完成工作。

本章就是从上述几个角度探讨编写文档/视结构应用程序的,只有真正解决了上述问题,才能编写出正确、高效的应用程序来。

猜你喜欢

转载自juejin.im/post/7048255631425273863