C语言实例教程(PDF格式)-第56部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
实现的框架,程序员需要根据应用程序的特定需求去添加相应的操
作。从本章后面几节的讲述来看,添加这些操作的复杂程度比过去小
…………………………………………………………Page 430……………………………………………………………
了很多。
可以使用Workspace窗口的ClassView查看AppWizard所生成的类和类
中的成员函数,从中我们可以看到AppWizard在Introduc应用程序的
视类和文档类中所重载的基类函数。在后面的章节中,我们需要修改
这些重载函数和添加新的成员函数来为特定的应用程序实现所需的功
能。
第三节 生成文档
在文档/视结构中,文档的任务通常是对数据进行管理和维护。我们
通常将数据保存在文档类的成员变量中。视可以直接或间接的访问文
档类中的这些成员变量,并通过这种方式来显示和更新数据。关于使
用文档类的成员变量来保存数据的详细介绍请参阅 “8。3。2 把数据
保存到成员变量中” 和 “8。3。3 使用集合类管理数据”。文档还负
责将数据保存到永久存储介质中。常见的情况是将数据保存到磁盘文
件或数据库中。在Visual C++ 的与文档/视结构相关的文档中,我们
称这个过程叫串行化 (serialize)。MFC类库为数据的串行化提供了默
认的支持,我们只需要在此基础中稍加修改就可以为自定义的文档类
提供串行化支持。在 “8。3。4 数据的串行化” 一节中讲述了实现一
般的串行化过程的方法和步骤。对象的串行化需要考虑一些额外的问
题,这在 “8。3。5 串行化对象” 中讲述。文档类还可以处理命令消
息,这里所谓的命令消息是指来自如菜单、工具栏按钮和加速键的
WM_MAND通知消息。与Windows消息和控件通知消息不同,命令消
息可以被多种对象处理,这些对象除了窗口和视外,还可以是文档、
文档模板或应用程序本身。除了WM_MAND外,文档不能处理其它的
Windows消息。
8。3。1 概述
所有的文档类都以CDocument类为其基类。CDocument类提供了文档类
所需要的最基本的功能实现。更重要的是,CDocument类为文档对象
以及文档和其它对象 (如视对象、应用程序对象以及框架窗口等)交互
的实现提供了一个框架。我们所做的工作基本上是在这个已有框架的
基础上,添加与特定应用程序相关的实现。
从CDocument类派生自己的文档类所需的典型步骤为:
1。 为每一个文档类型从CDocument类 (当然也可以是其它CDocument类
的派生类)派生一个相应的文档类。
…………………………………………………………Page 431……………………………………………………………
2。 为文档类添加成员变量。这些成员变量用来保存文档的数据,其
它对象 (如与文档相关联的视)直接或间接的访问这些成员变量来读取
或更新文档的数据。
3。 重载Serialize成员函数,实现文档数据的串行化。
如果您的应用程序只使用一种文档类型,那么,在创建应用程序工程
时,AppWizard已为我们完成了一部分工作。典型地,AppWizard为应
用程序框架生成一个CDocument类的派生类,在默认情况下该类的命
名依赖于工程的名称。然后,AppWizard在该文档类中重载了基类的
几个成员函数,包括OnNewDocument和Serialize等。但是,
AppWizard在这些重载函数中只是简单地调用基类的相应函数,您需
要根据自己的应用程序的需要来修改它们。
图8。6 示例程序Example的运行结果
8。3。2 把文档数据保存到成员变量中
在使用文档/视结构的应用程序中,我们通常使用文档类的成员变量
来保存文档的数据。并使其它的对象(如与文档相关联的视)可以访问
这些成员变量,从而实现了文档和其它对象(主要是视)的相互搭配使
用。
下面我们来看一个简单的例子。该示例程序运行时如图8。6所示。
1。 首先创建一个MFC AppWizard (exe)工程,并取名为Example。如
果需要了解如何使用AppWizard创建一个基于文档/视结构的多文档界
面应用程序框架,请参阅 “8。2。1使用AppWizard创建使用文档/视结
构的应用程序所需的步骤” 一节中的讲述。
…………………………………………………………Page 432……………………………………………………………
2。 在Workspace窗口的ClassView选项卡中展开Example classes,可
以看到AppWizard为Example程序生成的所有类。右击CExampleDoc
类,单击Add Member Variable。。。,在Variable Type框中输入成员
变量的类型CString,在Variable Declaration框中输入成员变量名
m_str。由于我们希望其它类的对象可以访问该成员变量,因此在
Access框中选择其访问类型为Public。单击 “OK”,Visual C++将
该成员变量的定义添加类的定义中。
再按照与上面的过程相同的方法,在类CExampleDoc中添加类型为
LOGFONT的公有成员变量m_lf。
我们也可以手动地将成员变量添加到类CExampleDoc的定义中。如下
面的步骤所示:
2'。 在Workspace窗口中的FileView选项卡中展开Example
files|Header Files。双击ExampleDoc。h,Visual C++将在代码编辑
窗口中打开文件ExampleDoc。h,这个文件包括了Example应用程序中
的文档类CExampleDoc的定义。
在其中的
// Attributes
public:
之后手工地输入
CString m_str;
LOGFONT m_lf;
您也许已经发现,在类CExampleDoc的定义中包括多个public块。事
实上,您可以把这些定义都放到同一个public块中,在定义中包括多
个public块只是为了区别开不同用途的公有成员。例如,在上面所示
的代码中,我们将成员变量m_str的定义放到Attributes块内;而如
果您是使用Add Member Variable。。。添加变量的话,Visual C++是将
它添加到Implementation块中。这只是为了便于程序的阅读和维护,
对于编译器而言,您将公有成员的定义放到哪一个public块中其结果
都是一样的,对于私有成员和保护成员也是一样。
3。 为了测试该程序中,我们在CExampleDoc的OnNewDocument成员变
量中为公有成员m_str赋以初值 “您好; 欢迎使用本程序 !”,并弹
出一个字体对话框让用户为该字符串选定字体。方法是使用下面的代
…………………………………………………………Page 433……………………………………………………………
码替换OnNewDocument的实现代码中的// TODO注释:
m_str=〃您好; 欢迎使用本程序!〃;
CFontDialog dlg;
dlg。GetCurrentFont(&m_lf);
// 将用户选定的字体信息填充到LOGFONT类型的结构m_lf中,以供视类使用
if(dlg。DoModal()==IDOK)
dlg。GetCurrentFont(&m_lf);
4。 再重复一下,在MFC应用程序中,文档类是和视类一起协作以完成
应用程序功能的。下面我们将为Example程序的视类CExampleView类
的OnDraw成员函数添加一些代码,以将文档类中的m_str成员变量的
内容显示到视的框架窗口中。关于视类的内容是在本章的 “8。4 生
成视” 一节中讲述的。在本节,为了使应用程序完整并且能够运
行,以反映我们对文档类所进行的一些操作,书中给出一些用于视类
的代码,并且,为了使章节的行文连贯和有重点,我们并不详细的讲
解这些代码。如果您还不是很了解视类的话,大可不必去在意这些代
码究竟都是怎样工作的,以及为什么要这样书写这些代码,把本书继
续看下去,这些代码都不会成其为问题。但若您现在很想了解这些内
容,那也不妨跳过去浏览一下 “8。4 生成视” 以及 “图形设备接
口”一章。
这里我们用下面的代码来替换类CExampleView的OnDraw成员函数。
// 获取当前客户区的大小
CRect rectClient;
GetClientRect(rectClient);
CSize sizeClient=rectClient。Size();
// 从文件中读取数据
CString str=pDoc…》m_str;
LOGFONT lf=pDoc…》m_lf;
// 使字体充满整个客户区
lf。lfHeight=sizeClient。cy;
lf。lfWidth=long(sizeClient。cx/str。GetLength());
…………………………………………………………Page 434……………………………………………………………
// 用当前字体信息生成CFont对象
CFont *pFont=new CFont;
pFont…》CreateFontIndirect(&lf);
// 改变当前所用字体,并保存旧字体
CFont *pOldFont=pDC…》SelectObject(pFont);
// 用新选定的字体绘制字符串 〃您好; 欢迎使用本程序!〃
CSize sizeTextExtent=pDC…》GetTextExtent(str);
pDC…》TextOut((sizeClient。cx…sizeTextExtent。cx)/2;
(sizeClient。cy…sizeTextExtent。cy)/2;
str);
// 恢复系统默认字体
pDC…》SelectObject(pOldFont);
在上面的示例程序中,我们在文档类中定义了两个公有的成员变量
m_str和m_lf,然后在视类的OnDraw成员函数中访问了这两个成员变
量,通过这些变量从文档中获取所要显示的字符串和所使用的字体。
很多时候我们常使用的是另外一种方法,即把成员变量定义为私有或
保护成员,然后添加存取该成员变量的函数。如下面的代码所示:
protected:
CString m_str;
public:
CString GetStr()
{
CString *pStr=new CString;
*pStr=m_str;
return *pStr;
}
这样的好处是可以防止数据成员被从类外部修改,从而维护了类中数
…………………………………………………………Page 435……………………………………………………………
据的安全。
l 注意:
l 不象很多资料上所说的那样,在GetStr函数中使用下面的代码,
并不能保证类中的保护成员绝对不会被从外部修改:
l CString GetStr()
l {
l return m_str;
l }
这时,如果类的使用者在外部使用了如下面的语句所示的强
制类型转换,
l CString& str=(CString&)pDoc…》GetStr()
l str=〃字符串将被修改 !〃
则可以通过引用str来修改类中的保护性成员m_str的值,读
者可以编写程序来 自行验证这一点。
但如果不使用强制类型转换将pDoc…》GetStr转换为CString&
并把str定义为CString (而不是CString&)的话,修改str并
不会改变类中的保护成员m_str的值。
l 使用以const关键字修饰的指针来返回指向私有或保护性数据成 员
的指针也并不总是安全的。例如,若GetStr函数的定义如下:
l const CString* GetStr()
l {
l return (const CString*)&m_str;
l }
那么,在类的外部,使用者同样可以使用强制类型转换被声
明为const的指针,从而修改私有成员m_str,如下面的代码
所示:
…………………………………………………………Page 436……………………………………………………………
l CString *pStr=(CString *)pDoc…》GetStr();
l *pStr=〃字符串将被修改 !〃;
l 虽然我们不应该过多地使用这种强制类型转换,但是,在编写类
的时候,还是要尽可能的避免可能出现这些不希望发生的事情,
以免用户对类的不正当的使用导致某些意外的问题,如使类中的
数据不再可用等。如果调用者对 自己的所做不是很清楚的话,这
很可能导致程序出错,并且难以被检查出来,因为很多的编程者
(尤其是初学者) 一般不容易想到问题的根源出在类的内部,尽管
这种问题是由于错误的使用类造成的。
与添加读取文档数据的公有函数相似,我们还可以添加设置文档数据
的公有成员函数,如下面的代码所示:
public:
int SetStr(const CString& NewStr)
{
m_str=NewStr;
}
使用公有成员函数来存取类中的数据的一个最大的好处在于可以验证
用户所传递的数据的有效性。从而避免用户把一个非法的数据赋给类
的成员变量,这有可能在后面的使用中导致意外的问题。
8。3。3 串行化数据
在Visual C++术语中,我们把对象的保存到永久介质中或从永久介质
中读取对象称作串行化。串行化的基本观点是每一个对象都应该能够
将 自身的当前数据保存到永久介质中,这些数据一般由其成员变量所
提供;在需要的时候,对象还应该能够从永久介质中读出所保存的数