144
第第第 第第第第第第第第

第七章 文档类对象持续性

  • Upload
    temira

  • View
    122

  • Download
    0

Embed Size (px)

DESCRIPTION

第七章 文档类对象持续性. 本章将要学习 MFC 应用程序框架另一个重要的机制 —— 对象的持续性 ,本章讲述的重点是对文档类所提 供的如何将 对象的数据保存到磁盘文件 中和如何从 磁 盘文件中保存的数据恢复对象数据 这一机制进行探讨 性的分析。. 7.1 CObject 根基类 MFC 中绝大部分的类都是直接或间接从根基类 CObject 派生 得到的,它们都自然地具备了 CObject 所具有的性质,当然用户 从这些 MFC 类派生的自定义类也同样继承了 CObject 的性质。 - PowerPoint PPT Presentation

Citation preview

Page 1: 第七章  文档类对象持续性

第七章 文档类对象持续性

Page 2: 第七章  文档类对象持续性

本章将要学习 MFC 应用程序框架另一个重要的机制

—— 对象的持续性,本章讲述的重点是对文档类所提

供的如何将对象的数据保存到磁盘文件中和如何从磁

盘文件中保存的数据恢复对象数据这一机制进行探讨

性的分析。

Page 3: 第七章  文档类对象持续性

7.1 CObject 根基类 MFC 中绝大部分的类都是直接或间接从根基类 CObject 派生

得到的,它们都自然地具备了 CObject 所具有的性质,当然用户

从这些 MFC 类派生的自定义类也同样继承了 CObject 的性质。

因此,理解 CObject 的性质就有着特别重要的意义。

7.1.1 CObject 类的三大性质 持续性、动态性和诊断性是面向对象的类要实现的十分重要

的功能。将实现这些功能的基本操作封装在根基类中,使之具

有这三方面的性质 , 从而导致它的派生类都能自然继承这些重要

性质是面向对象的类结构设计的精髓之一。

Page 4: 第七章  文档类对象持续性

1 持续性

所谓对象的持续性是指将内存中的对象数据保存到持久介质

(例如,磁盘文件),或者反过来,从持久介质(例如,磁盘

文件)中读取数据然后自动在内存中重建对象,这对于面向对

象程序中的数据读写是十分重要的性质。在 CObject 类中提供

持续化操作的是虚拟成员函数 Serialize( CArchive& ar ) 。它的派生

类都自动地继承这个虚函数,并且可以重定义派生类中此虚函

数的版本,以便实现特定数据的持续化操作。例如, CMyClass

是 CObject 的派生类,只要对 Serialize 进行重定义,便可以方便

地通过调用 Serialize 对 CMyClass 对象实现持续化操作。

Page 5: 第七章  文档类对象持续性

Class CMyClass : public CObject

{ …

protected:

Serialize( CArchive& ar );

};

其中参数 CArchive 类对象的引用 ar 提供了被打开的文件(用于

读或写)的相关信息和用于读写的缓冲区。只要在 Serialize 的

新版本中添加用于类对象数据持续化的代码,便可以通过 ar 实

现类对象与磁盘文件之间的读写操作,例如,下列代码就可以

完成读入文件中的数据对 CMyClass 类对象的初始化 —— 重建

持续化。 (假设 CMyClass::Serialize 已经定义)

Page 6: 第七章  文档类对象持续性

CMyClass *pOb = new CMyClass;

// 初始化 CArchive 类对象 ar

CFile file;

if(file.Open( “filename.dat”,CFile::modeRead ) )

// 文件 filename.dat 用于保存着描述 CMyClass 类对象的全部信息

{

CArchive ar( &file, CArchive::load );

pOb->Serialize(ar);

}

ar.Close();

file.Close();

下面的代码将实现 CMyClass 对象的数据保存到文件中 —— 保

存持续化操作。

Page 7: 第七章  文档类对象持续性

CMyClass Ob;

// 初始化 CMyClass 类对象 Ob

// 初始化 CArchive 类对象 ar

CFile file;

if(file.Open( “filename.dat”, CFile::modeCreate | CFile::modeWrite ) )

// 文件 filename.dat 用于保存描述 CMyClass 类对象的全部信息

{

CArchive ar( &file, CArchive::store );

Ob.Serialize( ar );

}

ar.Close();

file.Close();

Page 8: 第七章  文档类对象持续性

2 动态性

CObject 类所提供的动态性是最基本形式 —— 回答或者提供

类似“我是谁”的服务,但不提供动态性的高级形式 —— 动态创建。 CObject 的动态性是由成员函数 IsKindOf( CRunTimeCla

ss* ptr )

提供的。 CObject 类的派生类都继承了这一方法,只要调用该函

数,派生类对象便可以判断该类指针所指的对象是不是该类的

对象,例如:CMyClass *pOb;

pOb->IsKindOf( RUNTIME_CLASS( CMyClass ) );

// 如果是,则返回 TRUE ,否则返回 FALSE 。

Page 9: 第七章  文档类对象持续性

3. 诊断性

提供将对象的状态转储给调试机制(如 Debug 输出窗口)的

能力。 CObject 类有两种转储方式:

· 虚成员函数 Dump 将类对象的内部数据输出到 CDumpContext

类对象 afxDump 中,而 afxDump 是与调试输出窗口绑定的;

· 虚成员函数 AssertValid 用于判断数据的有效性。

AppWizard 会自动为 CObject 的派生类加入这两个成员函数的

重定义版本,例如定义一个 CObject 的间接派生类 CMyDoc (由

CDocument 直接派生),自动添加的重定义诊断函数代码:

Page 10: 第七章  文档类对象持续性

#ifdef _DEBUG

void CMyDoc::AssertValid() const

{

CDocument::AssertValid();

}

void CMyDoc::Dump( CDumpContext& dc ) const

{

CDocument::Dump( dc );

}

#endif // _DEBUG

假如 CMyDoc 类定义中具有两个数据成员:CString m_szName;

int m_nAge;

那么,可以将上述两个成员函数的定义代码修改为:

Page 11: 第七章  文档类对象持续性

void CMyDoc::AssertValid() const

{

ASSERT( !m_szName.IsEmpty() );

ASSERT( m_nAge < 0 );

CDocument::AssertValid();

}

void CMyDoc::Dump( CDumpContext& dc ) const

{

dc << “m_szName” << m_szName << endl;

dc << “m_nAge” << m_nAge << endl;

CDocument::Dump( dc );

}

Page 12: 第七章  文档类对象持续性

7.1.2 MFC 应用程序中的三对宏 微软公司在设计 MFC 类库的根基类 CObject 时,已经使该根

基类具有了上述三个重要性质,从而 MFC 中直接或间接地从

CObject 类派生的类都通过 CObject 类获得了持续性、动态性和

诊断性的机制和最基本功能。但是, CObject 对动态性和持续

性的支持是有限的,不能满足应用程序框架设计的需要,微软

公司便通过宏技术为 MFC 类设计了三对用于弥补这些不足的宏

定义。使用这三对宏定义能灵活地满足不同的应用需要。

Page 13: 第七章  文档类对象持续性

1 第一对宏 DECLARE_DYNAMIC – IMPLEMENT_DYNAMIC 和在类中的添加(以 CMyClass 为例) :class CMyClass : public CObject

{ …

protected:

CMyClass();

DECLARE_DYNAMIC( CMyClass )

};

IMPLEMENT_DYNAMIC 添加在类的实现文件中:IMPLEMENT_DYNAMIC( CMyClass, CObject )

这对宏的作用就是为所定义的派生类加入动态性的必要补充功能的代码:

Page 14: 第七章  文档类对象持续性

⑴ DECLARE_DYNAMIC 的定义(在系统头文件“ afx.h” 中)#define DECLARE_DYNAMIC( class_name ) \

protected: \

static CRuntimeClass* PASCAL _GetBaseClass(); \

public: \

static const AFX_DATA CRuntimeClass class##class_name; \

virtual CRuntimeClass* GetRuntimeClass() const; \

由此可知, DECLARE_DYNAMIC 在类中加入三个成员:·_GetBaseClass() 用于返回基类的 CRuntimeClass 类对象指针。· 该类的运行时信息类 CRuntimeClass 对象。其中 ## 可称为合 并操作符,其作用是将 class 和由参数 class_name 传递的类 名合并成 CRuntimeClass 类对象名。)·GetRuntimeClass() 用于返回该类的 CRuntimeClass 对象指针。

Page 15: 第七章  文档类对象持续性

⑵ IMPLEMENT_DYNAMIC 的定义(在系统头文件 “ afx.h” 中)#define IMPLEMENT_DYNAMIC( class_name, base_class_name ) \

IMPLEMENT_RUNTIMECLASS( class_name, base_class_name, 0xFFFF, NULL )

#define IMPLEMENT_RUNTIMECLASS( class_name, base_class_name, wSchema, pfnNew )

CRuntimeClass* PASCAL class_name::_GetBaseClass() \

{ return RUNTIME_CLASS(base_class_name); } \

AFX_COMDAT const AFX_DATADEF CRuntimeClass

class_name::class##class_name = {

#class_name, sizeof(class class_name), wSchema, pfnNew, \

&class_name::_GetBaseClass, NULL }; \

CRuntimeClass* class_name::GetRuntimeClass() const \

{ return RUNTIME_CLASS( class_name ); } \

由此可知, IMPLEMENT_DYNAMIC 在类中加入三个成员的实现代码。

Page 16: 第七章  文档类对象持续性

⑶ 将上述宏定义展开后 CMyClass 类的定义

在类的定义文件中,相当于增加了:class CMyClass : public CObject

{ …

protected:

CMyClass();

};

protected:

static CRuntimeClass* PASCAL _GetBaseClass();

public:

static const CRuntimeClass classCMyClass;

virtual CRuntimeClass* GetRuntimeClass() const;

DECLARE_DYNAMIC( CMyClass )

Page 17: 第七章  文档类对象持续性

IMPLEMENT_DYNAMIC(CMyClass, CObject)

在类的实现文件中,相当于增加了:

可见,加入 DECLARE_DYNAMIC – IMPLEMENT_DYNAMIC 的类将提供了下列动态性功能服务:· 能够支持 IsKindOf 服务。· 能提供该类及其基类的类名和大小。· 能提供该类及其基类的 CRuntimeClass 结构信息。

CRuntimeClass* PASCAL CMyClass::_GetBaseClass()

{ return RUNTIME_CLASS( CObject ); }

const CRuntimeClass CMyClass::classCMyClass =

{ “CMyClass”, sizeof(class CMyClass), 0xFFFF, NULL,

&CMyClass::_GetBaseClass, NULL };

CRuntimeClass* CMyClass::GetRuntimeClass() const

{ return RUNTIME_CLASS(CMyClass); }

Page 18: 第七章  文档类对象持续性

2 第二对宏 DECLARE_DYNCREATE – IMPLEMENT_DYNCREATE

在类中的添加 (以 CMainFrame 为例):class CMainFrame : public CFrameWnd

{ …

protected:

CMainFrame();

DECLARE_DYNCREATE( CMainFrame )

};

IMPLEMENT_DYNCREATE 添加在类的实现文件中:IMPLEMENT_DYNCREATE( CMainFrame, CFrameWnd )

这对宏的作用就是为所定义的派生类加入动态性的必要补充功能的代码:

Page 19: 第七章  文档类对象持续性

⑴ DECLARE_DYNCREATE 的定义(在系统头文件“ afx.h” 中)#define DECLARE_DYNCREATE( class_name ) \

DECLARE_DYNAMIC( class_name ) \

static CObject* PASCAL CreateObject();

可见,在添加了 DECLARE_DYNCREATE 的类定义中增加了:

·DECLARE_DYNAMIC 所提供的成员。

·CreateObject() 用于动态创建该类对象,并返回对象的指针。

⑵ IMPLEMENT_DYNCREATE 的定义(在系统头文件“ afx.h”

中)#define IMPLEMENT_DYNCREATE( class_name, base_class_name ) \

CObject* PASCAL class_name::CreateObject() \

{ return new class_name; } \

IMPLEMENT_RUNTIMECLASS( class_name,

base_class_name, 0xFFFF, class_name::CreateObject )

Page 20: 第七章  文档类对象持续性

⑶ 将上述宏定义展开后 CMainFrame 类的定义

在类的定义文件中,相当于增加了:class CMainFrame : public CFrameWnd

{ …

protected:

CMainFrame();

};

protected:

static CRuntimeClass* PASCAL _GetBaseClass();

public:

static const CRuntimeClass classCMainFrame;

virtual CRuntimeClass* GetRuntimeClass() const;

static CObject* PASCAL CreateObject();

DECLARE_DYNCREATE( CMainFrame )

Page 21: 第七章  文档类对象持续性

在类的实现文件中,相当于增加了:…

CObject* PASCAL CMainFrame::CreateObject() \

{ return new CMainFrame; } \

CRuntimeClass* PASCAL CMainFrame::_GetBaseClass()

{ return RUNTIME_CLASS(CFrameWnd); }

const CRuntimeClass CMainFrame::classCMainFrame = {

“CMainFrame”, sizeof( class CMainFrame ), 0xFFFF,

CMainFrame::CreateObject(),

RUNTIME_CLASS(CFrameWnd), NULL};

CRuntimeClass* CMainFrame::GetRuntimeClass() const

{ return &CMainFrame::classCMainFrame; }

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

Page 22: 第七章  文档类对象持续性

可见,添加了 DECLARE_DYNCREATE – IMPLEMENT_DYNCREATE

的类将提供比添加 DECLARE_DYNAMIC – IMPLEMENT_DYNAMIC

的类更强的动态性功能服务:

· 能够支持 IsKindOf 服务。

· 能提供该类及其基类的类名和大小。

· 能提供该类及其基类的 CRuntimeClass 结构信息。

· 能支持程序运行时动态创建对象的能力。

Page 23: 第七章  文档类对象持续性

3 第三对宏 DECLARE_SERIAL – IMPLEMENT_SERIAL 和在类中的

添加:

这对宏的作用除了提供对象的动态性的增强功能,还提供了

持续性的增强功能,与 CObject 类提供的持续性功能比较,有

哪些增强呢?我们通过分析这对宏的定义,和一个从 CObject

派生的自定义类 CMyClass 添加了这对宏所发生的变化加以了

解。与前两对宏一样, DECLARE_SERIAL 被添加在自定义类的类

定义中,例如在 CMyClass 的定义代码中:

Page 24: 第七章  文档类对象持续性

class CMyClass : public CObject

{ …

protected:

CMyClass();

DECLARE_SERIAL( CMyClass )

public:

virtual void Serialize( CArchive& ar );

};

IMPLEMENT_SERIAL 添加类的实现中,例如:IMPLEMENT_SERIAL( CMyClass, CObject, 1 )

这对宏的作用就是为所定义的派生类加入必要的补充动态性和持续性功能的代码:

Page 25: 第七章  文档类对象持续性

⑴ DECLARE_SERIAL 的定义(在系统头文件 “ afx.h” 中)

#define DECLARE_SERIAL( class_name ) \

_DECLARE_DYNCREATE ( class_name ) \

AFX_API friend CArchive& AFXAPI \

operator>> ( CArchive& ar, class_name* &pOb );

可见,在添加了 DECLARE_SERIAL 的类定义中增加了:

· 提供了 DECLARE_DYNCREATE 所增加的成员。

·增加了一个友元函数 operator>> 的定义,表示支持使用运算

符 >> 从 CArchive 对象读取数据以重建该类对象的能力。

Page 26: 第七章  文档类对象持续性

⑵ IMPLEMENT_SERIAL 的定义(在系统头文件“ afx.h” 中)

#define IMPLEMENT_SERIAL(class_name,base_class_name, wSchema) \

CObject* PASCAL class_name::CreateObject() \

{ return new class_name; } \

_IMPLEMENT_RUNTIMECLASS( class_name, base_class_name, \

wSchema, class_name::CreateObject ) \

AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS( class_name )); \

CArchive& AFXAPI operator>>( CArchive& ar, class_name* &pOb ) \

{ pOb=(class_name*)ar.ReadObject(RUNTIME_CLASS( class_name )); \

return ar; } \

Page 27: 第七章  文档类对象持续性

⑶ 将上述宏定义展开后 CMyClass 类的定义相当于增加了:class CMyClass : public CObject

{ …

protected:

CMyClass();

};

protected:

static CRuntimeClass* PASCAL _GetBaseClass();

public:

static const CRuntimeClass classCMyClass;

virtual CRuntimeClass* GetRuntimeClass() const;

static CObject* PASCAL CreateObject();

AFX_API friend CArchive& AFXAPI

operator>>( CArchive& ar, CMyClass* &pOb );

DECLARE_SERIAL( CMyClass )

Page 28: 第七章  文档类对象持续性

在类的实现文件中,相当于增加了:…

CObject* PASCAL CMyClass::CreateObject() \

{ return new CMyClass; } \

CRuntimeClass* PASCAL CMyClass::_GetBaseClass()

{ return RUNTIME_CLASS( CObject ); }

const CRuntimeClass CMyClass::classCMyClass = {

“CMyClass”, sizeof(class CMyClass), 1, CMyClass::CreateObject(),

RUNTIME_CLASS(CObject), NULL};

CRuntimeClass* CMyClass::GetRuntimeClass() const

{ return &CMyClass::classCMyClass; }

CArchive& AFXAPI operator>> ( CArchive& ar, CMyClass* &pOb ) {

pOb = ( CMyClass* )ar.ReadObject(RUNTIME_CLASS( CMyClass ));

return ar;

}

IMPLEMENT_SERIAL( CMyClass, CObject, 1 )

Page 29: 第七章  文档类对象持续性

可见,添加了 DECLARE_SERIAL – IMPLEMENT_SERIAL 的类能提

供下列动态性和持续性功能服务:

· 能够支持 IsKindOf 服务。

· 能提供该类及其基类的类名和大小。

· 能提供该类及其基类的 CRuntimeClass 结构信息。

· 能支持程序运行时动态创建对象的能力。

· 支持使用 >>运算符重建该类对象的能力。

其中,重建该类对象的服务可以用下例表示:

Page 30: 第七章  文档类对象持续性

CMyClass *pOb;

// 初始化 CArchive 类对象 ar

CFile file;

if( file.Open( “filename.dat”,CFile::modeRead ) )

{

CArchive ar( &file, CArchive::load );

ar >> pOb; // 等效于创建 CMyClass 对象,然后执行 pOb->Serialize

(ar)

}

ar.Close();

file.Close();

Page 31: 第七章  文档类对象持续性

需要说明的是: ar >> pOb 是一个复杂操作过程,它包括了:

·首先从文件中保存的 CRuntimeClass 结构中获取类对象的运行

时信息;

· 然后使用该类的 CreateObject 函数创建一个对象,并将该对象

的地址返回给 pOb ;

· 从文件中读取对象属性的信息,为对象初始化。

注意:同样可以用 ar << pOb 将对象数据信息保存到文件中。

Page 32: 第七章  文档类对象持续性

4 小结:

派生类 诊断功能 动态类信息 动态创建 对象重建

仅从 CObject 派生 支持 部分支持 不支持 不支持

从 CObject 派生 , 并添加DECLARE_DYNAMIC –

IMPLEMENT_DYNAMIC

支持 支持 不支持 不支持

从 CObject 派生 , 并添加DECLARE_DYNCREATE –

IMPLEMENT_DYNCREATE

支持 支持 支持 不支持

从 CObject 派生 , 并添加DECLARE_SERIAL –

IMPLEMENT_SERIAL

支持 支持 支持 支持

Page 33: 第七章  文档类对象持续性

7.2 文档类持续性原理 MFC 应用程序框架基本类 —— 文档类的基类是 CDocument ,

它是 CCmdTarget 的直接派生类, CObject 的间接派生类。因

此, CDocument 类自然具有 CObject 类的三大特性。如果在它的

派生类中加入三对宏中的任意一个,就可以获得不同层次的动

态性和持续性支持。

文档类在 MFC 应用程序框架中的基本作用是完成应用程序的

数据保存和读取。因此,如何将数据的写入文件和如何从文件

读取数据是文档类中要实现的最主要的操作之一。实现的方法

按是否使用类的持续化功能可以分为两类:

Page 34: 第七章  文档类对象持续性

1 文档数据与磁盘文件之间的直接读写⑴ 使用 C 运行库的打开文件函数,如 fopen ,获得带有缓冲

区 的 FILE 指针,然后以该指针为参数,调用读写文件的库

函 数,如 fread 和 fwrite 以及 fseek (移动文件访问指针),

对文 件进行读写,操作结束时调用关闭文件库函数,如 fclose , 关闭文件并释放 FILE 指针。上述操作过程还可以使用 C

++

文件流的相应成员函数或 Win32 的其他的文件操作函数,如

_lopen 、 _lread 、 _lwrite 、 _lseek 和 _lclose 函数实现。但必须注

意,各类函数之间不可混用。这类操作图示如下:磁盘文件

磁盘文件操作函数

文档数据

Page 35: 第七章  文档类对象持续性

⑵ 创建一个 MFC 的 CFile 类对象,通过调用 CFile 类相应的功

能的成员函数 Open 、 Read 或 ReadHuge 、 Write 或 Writ

eHuge 、

Seek 和 Close 等进行磁盘文件的操作,从而实现文档的读写

操作。虽然在实质上与方法 ⑴ 没有什麽区别,但使用这种

方法更安全、更符合面向对象的程序设计方法。这类操作图

示如下:磁盘文件

CFile对象成员函数

文档数据

Page 36: 第七章  文档类对象持续性

2 使用 MFC 对象的持续化功能,实现文档和文件之间的读写

⑴ 隐式调用 Serialize 函数进行文档的读写。采用这类方法应用

程序不直接调用磁盘读写操作,而只依赖持续化处理过程,

避免了在程序中直接使用 CFile 对象。这是因为在 Seriali

ze 函

数和 CFile 对象之间使用一个归档对象( CArchive 类对象)进

行关联。 CArchive 对象是 CFile 对象的类型安全的数据缓存,

它同时还保存一个内部标记,用来标识归档是写文件还是读

文件。每次只能有一个活动的归档与被操作文件关联。应用

程序框架会在响应 File 菜单的 Open 、 Save 和 Save As

命令

时,很好地管理 CFile 对象及 CArchive 对象的自动创建,

Page 37: 第七章  文档类对象持续性

并为 CFile 对象打开选定的磁盘文件,再将相应的 CArchi

ve

对象与 CFile 对象相关联。而我们需要做的就是在 Seriali

ze

中,将数据插入到归档对象中或从归档对象中析取。在响应

File 菜单的 Open 、 Save 和 Save As 命令的过程中会调用指定

文档的 Serialize 函数。这类操作图示如下:

磁盘文件

CArcheive对象 CFile对象

文档对象

Serialize

数据

Page 38: 第七章  文档类对象持续性

⑵ 显式调用 Serialize 函数进行文档的读写。即不使用在进行文

件操作时应用程序框架自动产生的 CFile 对象和 CArchive

象,而是在相关文件操作响应函数中自己创建的 CFile 对象

和 CArchive 对象,并且为 CFile 对象打开文件和将 CArc

hive 对

象与文件相连,然后调用 Serialize 函数完成文档的读写操

作。

注意:使用持续化功能除了保存或恢复类中的数据成员外,还

将保存或恢复包括类的全部运行时信息。

Page 39: 第七章  文档类对象持续性

7.2.1 Serialize 函数原理 例如,在一个自定义文档类 CMyDoc 中包含了两个数据成员: CString m_szName 和 int m_nAge ,该文档类的持续化函数Serialize 的典型定义如下:void CMyDoc::Serialize( CArchive& ar )

{

if( ar.IsStoring() )

{ // storing code

ar << m_szName << m_nAge;

}

else

{ // loading code

ar >> m_szName >> m_nAge;

}

}

Page 40: 第七章  文档类对象持续性

Serialize 函数所进行的持续化操作是通过传入的参数 —— CArchive 类对象的引用 ar 实现的,它提供了:· 与被读写文件相关联的 CFile 类对象的指针。· 能判断持续化操作的方向,一个给定的 CArchive 类对象在某 一时刻只能读或者写,而不能同时提供读写操作。· 写操作时,数据被放到 ar 的缓冲区中,直到缓冲区满,才将 数据写入它的文件指针所指向的 CFile 对象,即文件中。· 读操作时,数据从 ar 的文件指针指向的 CFile 对象,即文件 中读到缓冲区,然后再从缓冲区读到可持续化的类对象中 (重建类对象)。 这种缓冲机制是类型安全的,减少了访问物理磁盘的次数,从而提高了应用程序的性能。 MFC 框架应用程序中, CArchive

类对象 ar 是由应用程序框架动态创建并完成初始化的。

Page 41: 第七章  文档类对象持续性

7.2.2 何时调用 Serialize 函数 一般情况下,只有在读取文件和保存文件时,才会发生文

档类对象的持续化,调用 Serialize 函数。1 建立或打开文档 菜单命令 File→New 菜单命令 File→Open

OnNewDocument()

DeleteContents()

SetModifiedFlag(FALSE)

OnOpenDocument()

GetFile(…)构造 CArchive对象

DeleteContents()

Serialize(…)

SetModifiedFlag(FALSE)

文档对象可用

Page 42: 第七章  文档类对象持续性

2 保存文档

从上述过程可以看出,实现文档对象持续化操作的 Serializ

e

和 DeleteContents 函数一般都需要重定义,完成特定的操作。当

文档数据被修改时,应该调用 SetModifiedFlag(TRUE) ,以便在文

件关闭时,提示用户是否保存修改后的文档数据。

菜单命令 File→Save 菜单命令 File→Save As

GetFile(…)构造 CArchive对象

Serialize(…)

SetModifiedFlag(FALSE)

文件保存命令完成

OnSaveDocument()

Page 43: 第七章  文档类对象持续性

7.2.3 Serialize 函数支持哪些数据类型直接持续化 在 Serialize 函数中能否使用插入运算符“ <<” 和析取运算符“ >>”

支持数据的持续化,取决于该数据类型是否对归档类 CArchive

的插入运算符“ <<” 和析取运算符“ >>” 运算符进行了重新定义:

1 对象指针和对象的持续化friend CArchive& operator<<( CArchive& ar, const CObject* pOb );

throw( CArchiveException, CFileException );

friend CArchive& operator>>( CArchive& ar, CObject *& pOb );

throw( CArchiveException, CFileException, CMemoryException );

friend CArchive& operator>>( CArchive& ar, const CObject *& pOb );

throw( CArchiveException, CFileException, CMemoryException );

Page 44: 第七章  文档类对象持续性

上述对象指针的类必须是 CObject 的派生类,因此,只要在需要持续化的 CObject 的派生类中加入宏定义 DECLARE_SERIAL ,

并在该派生类的实现中加入宏定义 IMPLEMENT_SERIAL ,该类对象就可以通过 CArchive 的插入操作实现写文件功能;而通过CArchive 的析取操作从文件中读数据并恢复对象,则需要使用

该类的指针。下面是使用对象和对象指针持续化的两种情况:⑴ 第一种情况:假如一个自定义类 CStudent 的定义如下:class CStudent : public CObject

{ …

public:

CString m_strName;

int m_nGrade;

CTranscript m_transcript;

};

Page 45: 第七章  文档类对象持续性

其中的自定义类 CTranscript 必须从 CObject 类派生,并对持续化

函数 Serialize 进行了重新定义,使得 CTranscript 对象 m_trans

cript

的属性数据能通过 CArchive 对象实现持续化操作。在这种情况

下, CStudent 的持续化函数 Serialize 就可以重定义如下:void CStudent::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{ ar << m_strName << m_nGrade; }

else

{ ar >> m_strName >> m_nGrade; }

m_transcript.Serialize(ar);

}

Page 46: 第七章  文档类对象持续性

上述代码表明 CStudent::Serialize 之所以能够从归档(文件)中载入学生信息,是因为在 CStudent::Serialize 被调用之前,存放学生信息的 CStudent 对象已经创建,其内嵌对象 CTranscript

对象 m_transcript 也随着 CStudent 对象的创建同时被创建。因此,在 CStudent 对象类对象调用 Serialize 时,可以从归档对象 ar

中将相应数据载入属性成员 m_strName 和 m_nGrade 中,并通过 CTranscript::Serialize 函数将相应数据载入到 m_transcript 的相应属

性成员中。可见,对于 CObject 派生类的内嵌对象总是可以通过重定义持续化函数 Serialize , 并通过 Serialize 来实现与文件

之间的数据读写,但却不能实现从文件数据中自动重建类对象。⑵ 第二种情况:如果 CStudent 对象中包含的不是 CTranscript

的内嵌对象,而是 CTranscript 类的指针对象:

Page 47: 第七章  文档类对象持续性

class CStudent : public CObject

{

public:

CString m_strName;

int m_nGrade;

CTranscript *m_pTranscript;

};

并且使用宏 DECLARE_SERIAL - IMPLEMENT_SERIAL 对 CTransc

ript

的持续化功能进行了扩充:

Page 48: 第七章  文档类对象持续性

class CTranscript : public CObject

{

DECLARE_SERIAL(CTranscript)

void Serialize(CArchive &ar);

};

在 CTranscript 的实现代码中加入了 :

IMPLEMENT_SERIAL(CTranscript, CObject, 1)

使得 CTranscript 类指针指向的对象数据能被插入到 CArchive 类对

象关联的文件中,并能从 CArchive 类对象关联的文件中析取数

据、重建对象,使 CTranscript 类指针指向它。

Page 49: 第七章  文档类对象持续性

在这种情况下, CStudent::Serialize 函数的操作代码应按如下方

式来编写:void CStudent::Serialize(CArchive& ar)

{

if (ar.IsStoring())

ar << m_strName << m_nGrade << m_pTranscript;

else

ar >> m_strName >> m_nGrade >> m_pTranscript;

}

与前面的 CStudent::Serialize 函数定义比较, CTranscript 对象是如

何与文件进行数据读、写的呢?

Page 50: 第七章  文档类对象持续性

⑴ 当 CTranscript 对象被写入归档时,通过指针 m_pTranscript

用 CTranscript::Serialize 实现将类的运行时信息和属性数据一

起写入归档;

⑵ 当从归档读入时,这对宏所产生的代码将完成如下操作:

· 读入类 CTranscript 的运行时信息;

· 引用指针 m_pTranscript 动态创建相应类 CTranscript 对象;

· 一旦对象被创建,便可以被调用 CTranscript::Serialize 完成

将数据从归档读入对象。

Page 51: 第七章  文档类对象持续性

注意:

⑴ 为防止内存泄漏,当从归档读入时,必须保证 m_pTranscript

从归档对象析取数据之前不指向任何 CTranscript 对象,即在

CStudent 对象被创建时, m_pTranscript 应该置为空。

⑵ CArchive 类的插入和析取运算符不能用于 CObject 派生类的内

嵌对象。对 CTranscript 对象如下的使用是错误的:ar >> m_strName >> m_nGrade >> &m_transcript;

这是因为类对象的动态重建需要使用类指针的引用,而对象

的地址值是不能建立引用的。

Page 52: 第七章  文档类对象持续性

2. 简单数据类型的持续化CArchive& operator << ( BYTE by );

throw( CArchiveException, CFileException );

CArchive& operator << ( WORD w );

throw( CArchiveException, CFileException );

CArchive& operator << ( int i );

throw( CArchiveException, CFileException );

CArchive& operator << ( LONG l );

throw( CArchiveException, CFileException );

CArchive& operator << ( DWORD dw );

throw( CArchiveException, CFileException );

CArchive& operator << ( float f );

throw( CArchiveException, CFileException );

CArchive& operator << ( double d );

throw( CArchiveException, CFileException );

Page 53: 第七章  文档类对象持续性

CArchive& operator >> ( BYTE& by );

throw( CArchiveException, CFileException );

CArchive& operator >> ( WORD& w );

throw( CArchiveException, CFileException );

CArchive& operator >> ( int& i );

throw( CArchiveException, CFileException );

CArchive& operator >> ( LONG& l );

throw( CArchiveException, CFileException );

CArchive& operator >> ( DWORD& dw );

throw( CArchiveException, CFileException );

CArchive& operator >> ( float& f );

throw( CArchiveException, CFileException );

CArchive& operator >> ( double& d );

throw( CArchiveException, CFileException );

注意:可以被隐含转换为上述数据类型的简单数据类型也可以被持续化。

Page 54: 第七章  文档类对象持续性

3 结构变量的持续化CArchive& AFXAPI operator << ( CArchive& ar, SIZE size );

throw( CArchiveException, CFileException );

CArchive& AFXAPI operator << ( CArchive& ar, POINT point );

throw( CArchiveException, CFileException );

CArchive& AFXAPI operator << ( CArchive& ar, const RECT& rect );

throw( CArchiveException, CFileException );

CArchive& AFXAPI operator >> ( CArchive& ar, SIZE size );

throw( CArchiveException, CFileException );

CArchive& AFXAPI operator >> ( CArchive& ar, POINT point );

throw( CArchiveException, CFileException );

CArchive& AFXAPI operator >> ( CArchive& ar, const RECT& rect );

throw( CArchiveException, CFileException );

等。

Page 55: 第七章  文档类对象持续性

如果文档类中有一个非 CObject 类派生类的对象成员需要持

续化,它显然不包含在前面所述的任何一种可持续化类型中,

则必须把该类的数据成员分解成可持续化的基本类型逐一地进

行持续化操作。

Page 56: 第七章  文档类对象持续性

归纳 Serialize 函数的定义可以由三部分组成:

⑴ 调用直接基类的 Serialize 成员函数(如果基类中的 Serialize

中没有实际意义的操作,则可以不包括这部分)。

⑵ 根据归档对象的操作方向判别成员函数 IsStoring 或 IsLoading

的返回值确定调用插入或析取运算符将那些可以直接持续化

的对象写入归档对象或从归档对象中读出(重建)。

这些对象必须属于如下各类:

·CArchive 类已经对插入运算符 << 和析取运算符 >> 进行了

重载的 C++ 预定义类型对象;

· 已经对插入运算符 << 和析取运算符 >> 进行了重载的

MFC 类对象(例如: CString 、 CRect 、 CPoint 、 CSize等);

Page 57: 第七章  文档类对象持续性

· 已经对插入运算符 << 和析取运算符 >> 进行了重载的自

定义非 CObject 派生类象。例如:假定一个不是从 CObjec

t

派生的自定义类 CLine 有两个 CPoint 类对象数据成员 pt

pt1 ,它的插入运算符 << 和析取运算符 >> 的重载如下:

friend CArchive& operator << ( CArchive& ar, const CLine& line );

CArchive& operator << ( CArchive& ar, const CLine& line )

{ ar << line.pt << line.pt1; }

friend CArchive& operator >> ( CArchive& ar, CLine& line );

CArchive& operator >> ( CArchive& ar, CLine& line )

{ ar >> line.pt >> line.pt1; }

Page 58: 第七章  文档类对象持续性

· 可以对添加了 DECLARE_SERIAL - IMPLEMENT_SERIAL ,并重定义了 的 Serialize 的 CObject 派生类的指针调用插入和

析取运算符,以便完成该类指针指向的类对象写入归档对

象,和通过该类指针的引用从归档对象中读取数据重建类

对象。

⑶ 调用 CObject 派生的 MFC 基类或自定义类的 Serialize 成员函

数完成相应内嵌对象的持续化操作。

Page 59: 第七章  文档类对象持续性

7.2.4 文档类的其他虚成员函数1 virtual void CDocument::OnCloseDocument( );

Called by the framework when the document is closed, typically as part

of the File Close command. The default implementation of this function

destroys all of the frames used for viewing the document, closes the

view, cleans up the document's contents, and then calls the

DeleteContents member function to delete the document’s data.

Override this function if you want to perform special cleanup processing

when the framework closes a document. For example, if the document

represents a record in a database, you may want to override this

function to close the database. You should call the base class version

of this function from your override.

Page 60: 第七章  文档类对象持续性

2 virtual BOOL CDocument::CanCloseFrame( CFrameWnd* pFrame );

Called by the framework before a frame window displaying the

document is closed. The default implementation checks if there are

other frame windows displaying the document. If the specified frame

window is the last one that displays the document, the function

prompts the user to save the document if it has been modified.

Override this function if you want to perform special processing when a

frame window is closed. This is an advanced overridable.

3 virtual void CDocument::PreCloseFrame( CFrameWnd* pFrame );

This member function is called by the framework before the frame

window is destroyed.  It can be overridden to provide custom cleanup,

but be sure to call the base class as well.

Page 61: 第七章  文档类对象持续性

4 virtual BOOL CDocument::SaveModified( );

Called by the framework before a modified document is to be closed.

The default implementation of this function displays a message box

asking the user whether to save the changes to the document, if any

have been made. Override this function if your program requires a

different prompting procedure. This is an advanced overridable.

Page 62: 第七章  文档类对象持续性

7.2.5 实现对象持续化的几种方法举例 为了更清楚地了解如何实现对象的持续化操作,我们在上

一章的实例 “ DrawDraw” 的基础上,对程序中的 CLine 类对象采用多种

方法实现持续化操作进行比较:对 CLine 类对象的持续化操作主要表现在如何将类对象的属性值写入文件和如何从文件中恢复属性值。 CLine 类的属性定义如下:class CLine : public CObject

{ …

public:

CPoint m_pt, m_pt1;

};

Page 63: 第七章  文档类对象持续性

1 方法 1 (实例 “ Draw” ):该方法是将 CLine 类对象的属性 m_pt

和 m_pt1 分解为可以直接持续化操作的预定义类型变量,因

此无须对 CLine 类进行任何修改和添加。 CDrawDoc::Serialize

中对 CLine 类对象的持续化操作代码如下:

Page 64: 第七章  文档类对象持续性

void CDrawDoc::Serialize(CArchive& ar)

{

CLine *pl;

if (ar.IsStoring())

{

ar << (short)m_ptList.GetCount();

POSITION pos = m_ptList.GetHeadPosition();

while(pos != NULL)

{

pl = (CLine*)m_ptList.GetNext(pos);

ar << pl->m_pt.x << pl->m_pt.y;

ar << pl->m_pt1.x << pl->m_pt1.y;

}

}

Page 65: 第七章  文档类对象持续性

else

{

short count;

ar >> count;

for(short i = 0; i < count; i++)

{

pl = new CLine;

ar >> pl->m_pt.x >> pl->m_pt.y;

ar >> pl->m_pt1.x >> pl->m_pt1.y;

m_ptList.AddTail(pl);

}

}

}

Page 66: 第七章  文档类对象持续性

2 方法 2 (实例 “ Draw1” ):该方法是利用 CLine 类对象的属性

m_pt 和 m_pt1 是可以直接持续化操作的 MFC 类,因此也不需

要对 CLine 类进行任何修改和添加。在 CDrawDoc::Serialize

对 CLine 类对象的持续化操作代码如下:

Page 67: 第七章  文档类对象持续性

void CDrawDoc::Serialize(CArchive& ar)

{

CLine *pl;

if (ar.IsStoring())

{

ar << (short)m_ptList.GetCount();

POSITION pos = m_ptList.GetHeadPosition();

while(pos != NULL)

{

pl = (CLine*)m_ptList.GetNext(pos);

ar << pl->m_pt << pl->m_pt1;

}

}

Page 68: 第七章  文档类对象持续性

else

{

short count;

ar >> count;

for(short i = 0; i < count; i++)

{

pl = new CLine;

ar >> pl->m_pt >> pl->m_pt1;

m_ptList.AddTail(pl);

}

}

}

Page 69: 第七章  文档类对象持续性

3 方法 3 (实例 “ Draw2” ):该方法是使 CLine 类对象能插入

CArchive 类对象和能从 CArchive 类对象析取,从而实现 CLin

e

类对象的持续化操作,因此首先需要为 CLine 类添加如下的

友元函数定义: friend CArchive& operator <<(CArchive& ar, const CLine& ln)

{

ar << ln.m_pt << ln.m_pt1;

return ar;

}

friend CArchive& operator >>(CArchive& ar, CLine& ln)

{

ar >> ln.m_pt >> ln.m_pt1;

return ar;

}

Page 70: 第七章  文档类对象持续性

然后在 CDrawDoc::Serialize 中添加对 CLine 类对象的持续化操

作代码如下: void CDrawDoc::Serialize(CArchive& ar)

{

CLine *pl;

if (ar.IsStoring())

{

ar << (short)m_ptList.GetCount();

POSITION pos = m_ptList.GetHeadPosition();

while(pos != NULL)

{

pl = (CLine*)m_ptList.GetNext(pos);

ar << *pl;

}

}

Page 71: 第七章  文档类对象持续性

else

{

short count;

ar >> count;

for(short i = 0; i < count; i++)

{

pl = new CLine;

ar >> *pl;

m_ptList.AddTail(pl);

}

}

}

注意,对于前三种实现持续化的方法, CLine 均无须是 CObject

的派生类。

Page 72: 第七章  文档类对象持续性

4 方法 4 (实例 “ Draw3” ):该方法是通过为 CLine 类添加宏

DECLARE_SERIAL – IMPLEMENT_SERIAL 使 CLine 类对象能通过

指针被写入文件并能从文件中重建类对象。因此首先需要为 CLine 类添加宏,并重定义持续化虚函数 Serialize : 在 CLine 类的定义文件中添加: class CLine : public CObject

{

DECLARE_SERIAL(CLine)

public:

virtual void Serialize(CArchive& ar);

};

Page 73: 第七章  文档类对象持续性

在 CLine 类的实现文件中添加:

IMPLEMENT_SERIAL (CLine, CObject, 1)

重定义 Serialize 的代码如下: void CLine::Serialize(CArchive &ar)

{

if(ar.IsStoring())

ar << m_pt << m_pt1;

else

ar >> m_pt >> m_pt1;

}

然后在 CDrawDoc::Serialize 中添加对 CLine 类对象的持续化操

作代码如下:

Page 74: 第七章  文档类对象持续性

void CDrawDoc::Serialize(CArchive& ar)

{

CLine *pl;

if (ar.IsStoring())

{

ar << (short)m_ptList.GetCount();

POSITION pos = m_ptList.GetHeadPosition();

while(pos != NULL)

{

pl = (CLine*)m_ptList.GetNext(pos);

ar << pl;

}

}

Page 75: 第七章  文档类对象持续性

else

{

short count;

ar >> count;

for(short i = 0; i < count; i++)

{

ar >> pl;

m_ptList.AddTail(pl);

}

}

}

Page 76: 第七章  文档类对象持续性

5 方法 5 (实例 “ Draw4” ):该方法利用了 CTypedPtrList 对象能

对保存在链表中的具有持续化功能的对象指针自动进行持续

化操作的功能,实现对存放在链表中的 CLine 类对象的持续

化操作的。因此与方法 4 相同,首先需要为 CLine 类添加扩

展持续性功能的宏,并重定义持续化虚函数 Serialize 。然后

在文档类的持续化函数 CDrawDoc::Serialize 中添加对

CTypedPtrList 对象的持续化操作代码如下: void CDrawDoc::Serialize(CArchive& ar)

{

m_ptList.Serialize(ar);

}

Page 77: 第七章  文档类对象持续性

7.3 文档类持续性的局限性 使用应用程序文档类的 Serialize 函数虽然能够满足绝大多

数情况下文档数据的安全保存和读取恢复,并且具有无需直接对文件进行操作的优点;但也由于它的实现机制所决定的,使得使用 Serialize 对文件的读写不够灵活,因此,在有些情况下Serialize 所实现的持续性就不能满足对数据读写操作的要求。

7.3.1 Serialize 函数不能适应的情况1 Serialize 只能顺序读写,不能随机读写文件。2 Serialize 必须一次读写文件的全部,不能部分读写文件。3 Serialize 只能操作二进制文件,不能处理文本文件,或者说 它产生的文件不能像文本文件那样可读。4 Serialize 不能操作数据库文件。5 Serialize 不能以共享方式操作文件。

Page 78: 第七章  文档类对象持续性

7.3.2 解决办法1 支持随机和部分读写文件 创建与所要读写的文件相关联的 CFile 类对象,通过使用CFile::Open ,CFile::Read , CFile::Write , CFile::Seek 等成员函数实现对文件的随机和部分读写操作。

2 操作文本文件 MFC 的 CStdioFile 类支持文本操作,产生的文件可读。可以通过重定义文档类的虚函数 OnOpenDocument 、 OnNewDocumen

t

和 OnSaveDocument ,使用 CStdioFile 类对象替代原来应用程序

框架中的 CFile 类对象。 CStdioFile 类详细信息,查询 MSDN

中的有关部分。

Page 79: 第七章  文档类对象持续性

3 访问数据库文件解决共享读写文件

重载文档类的虚函数 OnOpenDocument 、 OnNewDocument

OnSaveDocument 访问数据库文件,把对数据库的操作和并发控

制等任务交给数据库管理系统去做。在 OnOpenDocument 、

OnNewDocument 和 OnSaveDocument 中只要按照数据库访问接

口编写读写数据的代码即可。第十一章中将详细介绍如何从数

据库读写数据的内容。

Page 80: 第七章  文档类对象持续性

7.4 文档 - 视图结构及其意义 文档 - 视图结构是 MFC 应用程序框架提供的重要特点

之一,

这一结构可以描述为:

· 文档:是应用程序数据元素的集合,它构成了应用程序所使

用的数据源,并提供了管理和维护这些数据的手段。文档类

对象负责来自所有数据源(大多数情况下数据源中的数据来

自磁盘文件,但也可以来自串行口、网络、摄象机、扫描仪

等)的数据的各种操作。

·视图:是数据的显示窗口,它的最根本的职能是为用户提供

了可视的文档数据显示,即把文档的部分或全部内容在窗口

中显示出来。

Page 81: 第七章  文档类对象持续性

· 关系:一个文档允许有一个或多个视图,以便实现同一数据

源的不同形式的显示,相应地,一个文档类对象也可以对应

多个不同的视图类对象。而一个视图只允许对应一个文档,

相应地,一个视图类对象只能与一个文档类对象相关。

· 种类: MFC 提供了两种不同种类文档 - 视图结构:

⑴ 单文档 - 视图( SDI ):在应用程序的框架中同时只允许有一

个文档存在,打开一个新文档就自动关闭当前的文档,但文

档可以与多个视图相关联。由于视图窗口的框架窗口就是应

用程序的主框架窗口,所以,如果要同时显示多个视图,就

要使用窗口切分。

Page 82: 第七章  文档类对象持续性

⑵ 多文档 - 视图( MDI ):在应用程序框架中允许同时有多个文

档存在,这些文档的种类既可以相同(如 Word 应用程序中

同时存在的多个被编辑的文档)也可以不同(如一个医院的

诊断信息系统中同时打开就可能既有患者病案、医师诊断等

文本文档,又有患者的各种医学影象的图象文档)。每个

文档都可以与多个视图相关联。由于每个视图窗口都可以有

属于自己的子框架窗口,所以程序的主框架窗口中可以同时

显示多个视图窗口。因此,在应用程序的主菜单中要增加一

个 Window 子菜单进行对所有视图窗口;并 File 子菜单中增

加 Close 菜单项用于关闭当前激活的文档和所有关联视图。

Page 83: 第七章  文档类对象持续性

文档 - 视图结构的最大特点就是把数据操作和数据表示分离

开来,该结构简化了多数应用程序的设计开发过程,另外文档 -

视图结构还有以下特点:

1 文档 - 视图结构使程序进一步模块化

将数据操作和数据显示分离的思想,使得程序模块的划分更

加合理、模块的相对独立性更强,相对简化了数据操作和数据

显示的工作。文档只负责数据管理和操作,不涉及如何表示;

而视图负责数据的表示,不需要考虑应用程序的数据是如何产

生的,甚至文档中的数据结构发生了变化时也不必修改视图的

代码。

Page 84: 第七章  文档类对象持续性

2 有利于代码重用

文档 - 视图结构便于 MFC 可以提供了许多标准的操作界面,

例如,标准的菜单相应的新建文件、打开文件、保存文件、打

印、打印预览和电子邮件发送等功能,用户只需要重用而不必

开发这些重复代码。

3 不采用文档 - 视图结构的场合

⑴ 一些工具程序(例如磁盘扫描程序、时钟程序等),控制程

序等重在功能,而不是面向数据或者说数据量很少的应用程

序,最好不采用文档 - 视图结构,避免增加不必要的开销。

Page 85: 第七章  文档类对象持续性

⑵ 不需要重用 MFC 提供的标准用户界面功能的程序,如一些

游戏、专用仪器设备的操作管理程序等,最好不采用文档 -

视图结构。

Page 86: 第七章  文档类对象持续性

7.5 文档和视图的相互作用 在 MFC 应用程序框架中,文档视图结构的关系主要体现在

档类和视图类对象的相互作用和相互访问上。

CView::GetDocument 和 CDocument::UpdateAllViews 是实现这种

相互作用和相互访问的关键成员函数。

·CView::GetDocument() 函数返回文档类对象的指针,这对于在

视图中表示文档数据非常有用。

·CDocument::UpdateAllViews(CView *pSender, LPARAM lHint = 0,

CObject *pHint =NULL)

Page 87: 第七章  文档类对象持续性

该函数通过调用虚函数 CView::OnUpdate 使与该文档相关联的

所有视图窗口失效并在接收到下一个 WM_PAINT 消息时更新视

图窗口。因此,当用户在一个视图类对象中修改文档数据后,

本视图中数据表示将发生改变后,为了使与文档相关联的其他

视图中数据表示也与更新的文档内容保持一致,便可以通过所

获得的文档类对象指针调用 UpdateAllViews 实现这一操作。

Page 88: 第七章  文档类对象持续性

调用时的各参数:

pSender :是发出更新通知的视图类对象指针;如果 pSender 为

NULL表明通知与文档相关联的所有视图更新,这也意味着

该函数的调用也可以发生在文档类对象中或其他任何可以

正确安全地获取文档的类对象中;如果 pSender 不为 NU

LL

表示通知除 pSender 指向的视图以外的与文档相关联的所

有视图更新;所有得到更新通知消息的视图类对象将调用

成员函数 OnUpdate 完成更新。

lHint 和 pHint 是关于视图更新内容的提示。它们被传递给成员函

数 CView::OnUpdate 。 lHint 可以自定义含义, pHint 是一个

CObject 指针,因此几乎可以能够指向 MFC 所有的类对象,

Page 89: 第七章  文档类对象持续性

例如,它可以指向视图的更新区域,从而避免全部区域刷

新带来的屏幕闪烁。因此,当不使用 lHint 和 pHint 的缺省

值时一般需要重载 CView::OnUpdate 。能导致视图得到刷新的函数还有:

void CWind::Invalidate( BOOL bErase = TRUE );

void CWind::InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE );

void CWind::InvalidateRgn( CRgn* pRgn, BOOL bErase = TRUE );

只要获取了指向某一视图类对象的指针便可以调用上述三个成

员函数之一更新该视图的全部或部分区域。

Page 90: 第七章  文档类对象持续性

7.6 实例 1 :简单的文本编辑器 Editor

本例将制作一个简单的文本编辑器,用以说明文档 - 视图结

构的原理及应用。在这个编辑器中,用户只能逐行输入字符,

按回车键结束一行并换行,不支持字符的删除和插入,也没有

光标指示当前位置。但是,用户可以选择编辑器显示文本时所

使用的字体。

1 生成项目

使用 AppWizard 生成一个名为 “ Editor” 的 SDI 应用程序项目。

在创建的绝大部分过程中都使用默认选择,只在 step 4 of 6 对话

框中,单击 Advanced 按钮,弹出如下 Advanced Option 对话框,

该对话框用来设置文档 - 视图结构和主框架窗口的一些属性。

Page 91: 第七章  文档类对象持续性
Page 92: 第七章  文档类对象持续性

在该对话框中有两个标签页,一页是 Document Template String

用于设置文档模板的一些属性,这些属性与应用程序中定义文

档模板类对象的文档模板描述串 IDR_MAINFRAME 相对应。该描

述串可以在资源 String Table 的第一行处找到:

Page 93: 第七章  文档类对象持续性

在本例中文档模板描述串 IDR_MAINFRAME 为:"Editor\n\nEditor\nEditor 文件 (*.txt)\n.txt\nEditor.Document\nEditor Document"

该描述串由 7 段组合而成每段之间以 ’ \n’ 隔开,各段含义如下:

Editor\n // application window caption

\n // root for default document name(“Untitled” used if none prov

ided)

Editor\n // document type name

Editor 文件 (*.txt)\n // document type description and filter

.txt\n // extention for document of this type

Editor.Document\n // Registry file type ID

Editor Document // Registry file type description

该描述串中的大多数段都可以随着 Advance Option 对话框的

Document Template String 页各文本框内容的设置而改变:

Page 94: 第七章  文档类对象持续性

·File extension :指定应用程序创建的文档所用的文件名扩展

名。(本例中该项输入了 txt ,表明 Editor 使用文本文件的扩

展名为 .txt )

·File type ID :用于在 Windows 的注册数据库中标识应用程序的

文档类型。

·Main frame caption :主框架标题,默认情况与项目名相同。

·Doc type name :文档模板类型名,指定与一个从 CDocument

派生的文档类相关的文档模板标识名。在 MDI 应用程序中,

文档模板类型名可用于判别不同的文档模板。

·Filter name :用于“打开文件”、“保存文件”对话框中的查找文

件类型的过滤。

Page 95: 第七章  文档类对象持续性

·File new name(short name) :用于指定在 new 对话框中使用的

文档名。在 MDI 应用程序中,选择 File->New 菜单项时,会

弹出一个对话框,列出应用程序所支持的所有文档模板类

型,供用户选择。

·File type name(long name) :用于指定当应用程序作为 OLE

Automation 服务器时使用的文档类型名,使用默认值。

注意,上述文档模板描述串的各段描述均可以在资源编辑器中

进行修改。

Advance Option 对话框的另一个标签页 Window Style 用于设置

主框架窗口和子框架窗口的一些属性。

Page 96: 第七章  文档类对象持续性

2 查看生成的支持文档视图结构的相关代码

本例与文档 - 视图结构相关的类有 CEditorApp 、 CMainFr

ame 、

CEditorDoc 和 CEditorView ,它们分别是 CWinApp 、 CFrameWnd 、

CDocument 和 CView 的派生类。

⑴ CEditorApp 的消息映射表

在应用程序类 CEditorApp 中, AppWizard 将 File 菜单的 New 、

Open 和 Printer Setup 自动映射到基类 CWinApp 的成员函数:

OnFilenew 、 OnFileOpen 和 OnFilePrintSetup 。 CEditorApp 有关

的消息映射表代码如下:

Page 97: 第七章  文档类对象持续性

BEGIN_MESSAGE_MAP(CEditorApp, CWinApp)

//}}AFX_MSG_MAP

// Standard file based document commands

ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

// Standard print setup command

ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

END_MESSAGE_MAP()

注意这三条消息映射项在映射表中的位置在 AFX_MSG_MA

P

标注的代码块之外,使得它们在 ClassWizard 中是不可见的,

从而避免了对它们轻易地进行修改。

Page 98: 第七章  文档类对象持续性

⑵ CSingleDocTemplate 文档模板类 一个应用程序可以管理一个( SDI )或多个( MDI )文档模

板。每个文档模板可以创建和管理一个或多个同种类的文 档。文档模板类 CDocTemplate 是一个抽象类,因此必须使

用 它的派生类 CSingleDocTemplate 或 CMultiDocTemplate 来

创建单 或多文档模板对象。 文档模板定义了文档、视图和框架窗口这三个类在文档视图

结构中的关系。文档模板保存了这三个类的 CRuntimeClas

s 对 象的指针,还保存了所支持的文档类的全部信息,包括文

档 的文件扩展名信息、文档在框架窗口中显示的名字、代表

文 档的图标等信息。本例中在 CEditorApp 的成员函数 InitIns

tance

中实现文档模板的代码如下:

Page 99: 第七章  文档类对象持续性

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CEditorDoc),

RUNTIME_CLASS(CMainFrame), // main SDI frame window

RUNTIME_CLASS(CEditorView));

AddDocTemplate(pDocTemplate);

Page 100: 第七章  文档类对象持续性

3 修改文档类

⑴ 定义文档类的数据成员

在 CEditorDoc 类中加入能动态保存和管理文本字串的字符串

列表类 CStringList 对象数据成员和用于指示当前编辑行号的

整型数据成员 :

class CEditorDoc : public CDocument

{

protected: // create from serialization only

CEditorDoc();

DECLARE_DYNCREATE(CEditorDoc)

// Attributes

Page 101: 第七章  文档类对象持续性

public:

CStringList m_Lines; // the list for saving lines of string

int m_nLineNum; // the number of a specified string lin

e

};

⑵ 重载 DeleteContents()

在用 File -> Open 菜单项打开一个文档或关闭应用程序时,都

需要清除文档对象中的数据。这些工作是由程序框架调用

CDocument::DeleteContents() 虚函数完成的。因此,必须使用

ClassWizard 或在 Workspace 的 ClassView 中重定义此虚函数,

并加入如下代码:

Page 102: 第七章  文档类对象持续性

void CEditorDoc::DeleteContents()

{ // TODO: Add your specialized code here and/or call the base class

m_nLineNum = 0;

POSITION pos;

pos = m_Lines.GetHeadPosition();

while(pos != NULL)

{

((CString)m_Lines.GetNext(pos)).Empty(); // to empty a line

}

m_Lines.RemoveAll();

CDocument::DeleteContents();

}

Page 103: 第七章  文档类对象持续性

⑶ 初始化文档类的数据成员

在已经生成的成员函数 OnNewDocument 中加入如下代码,用

于创建新文档时,对文本字串列表和编辑行号进行初始化。 BOOL CEditorDoc::OnNewDocument()

{

if (!CDocument::OnNewDocument()) // 调用基类函数版本

return FALSE;

DeleteContents(); // 清除文档内容

return TRUE;

}

Page 104: 第七章  文档类对象持续性

⑷ 修改 Serialize 虚函数

为了实现文档类的文本字串列表中的数据持续化(读写相应

的文档文件)必须在已经生成的成员函数 Serialize 中添加如

下代码:

Page 105: 第七章  文档类对象持续性

void CEditorDoc::Serialize(CArchive& ar)

{

CString s("");

int nCount = 0;

CString item("");

if (ar.IsStoring())

{

POSITION pos;

pos = m_Lines.GetHeadPosition();

if(pos == NULL)

return;

Page 106: 第七章  文档类对象持续性

while(pos != NULL)

{

item = m_Lines.GetNext(pos);

ar << item;

item.Empty(); // clear the line buffer

}

}

Page 107: 第七章  文档类对象持续性

else

{

while(1)

{

try

{

ar >> item;

m_Lines.AddTail(item);

nCount++;

}

catch(CArchiveException *e)

{

if(e->m_cause != CArchiveException::endOfFile)

{

TRACE0("Unknown exception loading file!\n");

throw;

}

Page 108: 第七章  文档类对象持续性

else

{

TRACE0("End of file reached...\n");

e->Delete();

}

break; // break off the while(1)

} // end of catch

} // match of while(1)

m_nLineNum = nCount; // to update number of lines

} // match of else

}

Page 109: 第七章  文档类对象持续性

4 修改视图类

⑴ 为视图类添加数据成员 class CEditorView : public CView

{

protected: // create from serialization only

CEditorView();

DECLARE_DYNCREATE(CEditorView)

CFont* m_pFont; // pointer to the structure of selected font

int m_nlHeight; // height of text line

int m_ncWidth; // width of a char

};

Page 110: 第七章  文档类对象持续性

⑵ 视图类数据成员的初始化

视图类一般在成员函数 CView::OnInitialUpdate() 中初始化视图

类的数据成员。因为此时,视图窗口已经创建,所以可能影

响显示的视图类对象的数据成员一定要在这时被更新。在以

下情况下, OnInitialUpdate 函数将被框架自动调用来初始化

视图类对象的数据成员:

· 调用 CDocument::OnNewDocument 时;

· 调用 CDocument::OnOpenDocument 时,都会更新视图原有

的显示内容。

本例中需要使用 ClassWizard 为视图类 CEditorView 添加虚函

数 OnInitialUpdate ,并为它添加代码如下:

Page 111: 第七章  文档类对象持续性

void CEditorView::OnInitialUpdate()

{

// TODO: Add your specialized code here and/or call the base class

CDC *pDC = GetDC();

m_pFont = new CFont();

if(!m_pFont->CreateFont( 0,0,0,0,FW_NORMAL,FALSE,FALSE,

FALSE, ANSI_CHARSET,OUT_TT_PRECIS,CLIP_TT_ALW

AYS,

DEFAULT_QUALITY,DEFAULT_PITCH,"Courier New"))

{

m_pFont->CreateStockObject(SYSTEM_FONT);

}

Page 112: 第七章  文档类对象持续性

CFont* oldFont = pDC->SelectObject(m_pFont);

// to select new font and save old font

TEXTMETRIC tm;

pDC->GetTextMetrics(&tm);

m_nlHeight = tm.tmHeight + tm.tmExternalLeading;

m_ncWidth = tm.tmAveCharWidth;

pDC->SelectObject(oldFont); // restore old font

ReleaseDC(pDC);

CView::OnInitialUpdate();

}

Page 113: 第七章  文档类对象持续性

⑶ 修改析构函数 CEditorView::~CEditorView()

{

if(m_pFont)

delete m_pFont;

}

⑷ 修改 OnDraw 函数

OnDraw 是一个虚函数,当视图窗口收到 WM_PAINT 消息后,

消息处理函数 OnPaint 首先创建一个 CPaintDC 类对象 dc ,在

dc 的创建过程中,调用 BeginPaint 获取处理 WM_PAINT 消息

所需要的设备环境。

Page 114: 第七章  文档类对象持续性

如果设备是显示器,则将指向设备环境的指针作为参数调用

函数 OnDraw 完成在显示器上的绘图操作;如果设备是打印

机,则先调用 DoPreparePrinting 创建一个与打印机相匹配的

设备环境并将该设备环境指针传给 OnDraw 函数,完成打印

输出操作。注意, OnDraw 是 CView 特有的用于绘图的虚函

数,所以可以把 OnPaint 和 OnDraw 函数的联系总结如下:

· 在视图类中, OnDraw 函数是一个虚函数, OnPaint 把视图绘

制任务交给 OnDraw 函数,所以无需再定义派生类的 On

Paint

函数,而直接使用基类的 OnPaint 即可。

· 在其他窗口类(除视图类和视图类的派生类外),窗口的绘

制需要定义 OnPaint 函数。

Page 115: 第七章  文档类对象持续性

void CEditorView::OnDraw(CDC* pDC)

{

CEditorDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

CFont* oldFont;

oldFont = pDC->SelectObject(m_pFont);

TEXTMETRIC tm;

pDC->GetTextMetrics(&tm);

m_nlHeight = tm.tmHeight + tm.tmExternalLeading;

m_ncWidth = tm.tmAveCharWidth;

int y = 0;

POSITION pos;

CString line;

Page 116: 第七章  文档类对象持续性

if(!(pos = pDoc->m_Lines.GetHeadPosition()))

{

return;

}

// display each line of list

while(pos != NULL)

{

line = pDoc->m_Lines.GetNext(pos);

pDC->TextOut(0, y, line, line.GetLength());

y += m_nlHeight; // update coordinate y

}

pDC->SelectObject(oldFont); // to restore old font

}

Page 117: 第七章  文档类对象持续性

⑸ 响应键盘输入

为了实现从键盘不断地接收用户输入,必须为应用程序增加

键盘输入消息的响应处理,即将输入的字符加入到当前行的

末尾,并立即就把用户输入的内容在屏幕上显示出来。

使用 ClassWizard 在 CEditorView 中生成 WM_CHAR 消息的映射

和处理函数 OnChar ,并在 OnChar 的定义中添加如下代码:

Page 118: 第七章  文档类对象持续性

void CEditorView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{ // TODO: Add your message handler code here and/or call default

CEditorDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CClientDC dc(this);

CFont *oldFont;

oldFont = dc.SelectObject(m_pFont);

CString line("");

POSITION pos = NULL;

if(nChar == '\r')

// to increase number of lines encountering a carriage return

pDoc->m_nLineNum++;

else

{ // to get position of a string in text list rely on number of line

pos = pDoc->m_Lines.FindIndex(pDoc->m_nLineNum);

Page 119: 第七章  文档类对象持续性

if(!pos)

{ // to add a new character into text buffer and add a new lin

e into

// the text list if there isn't the string line in the text list.

line += (char)nChar;

pDoc->m_Lines.AddTail(CString(line));

}

else

{ // to add the string line into text list when a carrige return

// hasn't been encoutered.

line = pDoc->m_Lines.GetAt(pos);

line += (char)nChar;

pDoc->m_Lines.SetAt(pos, line);

}

Page 120: 第七章  文档类对象持续性

TEXTMETRIC tm;

dc.GetTextMetrics(&tm);

dc.TextOut(0, (int)pDoc->m_nLineNum * tm.tmHeight,

line, line.GetLength());

}

pDoc->SetModifiedFlag(TRUE);

dc.SelectObject(oldFont);

CView::OnChar(nChar, nRepCnt, nFlags);

}

Page 121: 第七章  文档类对象持续性

⑹ 定义选择字体的菜单项和消息处理代码

· 使用资源编辑器在主菜单的 View 菜单中增加一个菜单项:

菜单名: Select &Font

菜单标识: ID_SELECT_FONT

提示文本: Select a font for current view

· 使用 ClassWizard 在 CEditorView 中为此菜单项添加消息映射和

处理函数 OnSelectFont ,并为该处理函数添加代码:

Page 122: 第七章  文档类对象持续性

void CEditorView::OnSelectFont()

{

CFontDialog dlg;

if(dlg.DoModal() == IDOK)

{

LOGFONT lf;

dlg.GetCurrentFont(&lf); // Get information of selected

font

// Create a new font

m_pFont->DeleteObject();

m_pFont->CreateFontIndirect(&lf);

Invalidate(); // Invalidates the entire client area of CWnd.

UpdateWindow();

// Updates the client area by sending a WM_PAINT mess

age

}

}

Page 123: 第七章  文档类对象持续性

如果将对该菜单项的映射和处理函数添加到 CEditorDoc 类

中,则该字体将对与文档关联的所有视图有效;如果添加到

CEditorApp 类中,则该字体将对与所有创建的文档关联的所

有视图有效。当然必须将所选定的字体传递该视图类对象。

5 编译运行 “ Editor”

Page 124: 第七章  文档类对象持续性

7.7 实例 2 :具有滚动视图的 Editor 编辑器 实例 1 的视图不支持滚动,这就使得当文本超过当前视窗

小时,窗口将不能自动向上滚动以显示输入的字符。如果打开

的文件的内容显示超过当前视窗大小也无法通过滚动视图来查

看文档的全部内容。因此增加窗口滚动功能是十分必要的。

MFC 提供的 CScrollView 类简化了视图滚动所需要处理的大量

工作。除了管理文档中的滚动操作外,绘制滚动条、箭头和滚

动光标。它还负责处理:

· 初始化滚动条范围(通过滚动视图的 SetScrollRenge 方法);

·处理滚动条消息,并滚动文档到相应的位置;

Page 125: 第七章  文档类对象持续性

·管理窗口和视图的尺寸大小;

· 调整滚动条上的滑块 ( 或称拇指框 ) 的位置,使之与文档当前

位置相匹配。

如果需要应用程序支持视图滚动,需要进行下列编程工作:

· 从 CScrollView 类中派生出自己的视图类,以支持视图滚动;

· 提供文档可能的最大尺寸,确定滚动范围和设置初始位置;

·协调文档位置和屏幕坐标。

使用 AppWizard 生成支持视图滚动的框架程序,只要在生成

过程中选择 CScrollView 作为视图类的基类即可。实例 “ Editor1”

是通过对 Editor 程序中 CEditorView 的修改,使其支持视图滚

动。需要修改的地方包括:

Page 126: 第七章  文档类对象持续性

1 用 CScrollView 替换程序源代码中所有的 CView

2 设置文档的最大尺寸

要设置文档的最大尺寸,让 CScrollView 对象知道视图窗口的

滚动范围,确定何时滚动到文档的头部和尾部,以及当拖动滚

动条的滑块时按正确的比例调整整个文档当前的显示位置。为

此,需要在 CEditorDoc 类中增加:

· 在定义文件 EditorDoc.h 中增加数据成员 m_sizeDoc 和相应的

成员函数 GetDocSize :

Page 127: 第七章  文档类对象持续性

class CEditorDoc : public CDocument

{

protected: // create from serialization only

CEditorDoc();

DECLARE_DYNCREATE(CEditorDoc)

CSize m_sizeDoc;

// Attributes

public:

CStringList m_Lines; // the list for saving lines of string

int m_nLineNum; // the number of a specified string line

CSize GetDocSize(){ return m_sizeDoc; }

};

Page 128: 第七章  文档类对象持续性

· 在 CEditorDoc 类构造函数中对 m_sizeDoc 进行初始化:CEditorDoc::CEditorDoc()

{

// TODO: add one-time construction code here

m_nLineNum = 0;

m_sizeDoc = CSize(700, 800);

}

Page 129: 第七章  文档类对象持续性

3 设置视窗滚动范围

在 CEditorView::OnInitialUpdate 函数中调用 SetScrollSizes

函数

来设置视窗滚动范围:void CEditorView::OnInitialUpdate()

{ // TODO: Add your specialized code here and/or call the base class

CDC *pDC = GetDC();

m_pFont = new CFont();

if(!m_pFont->CreateFont(0,0,0,0,FW_NORMAL,FALSE,FALSE,FALSE,

ANSI_CHARSET,OUT_TT_PRECIS,CLIP_TT_ALWAYS,

DEFAULT_QUALITY,DEFAULT_PITCH,"Courier New"))

{

m_pFont->CreateStockObject(SYSTEM_FONT);

}

Page 130: 第七章  文档类对象持续性

CFont* oldFont = pDC->SelectObject(m_pFont);

// to select new font and save old font

TEXTMETRIC tm;

pDC->GetTextMetrics(&tm);

m_nlHeight = tm.tmHeight + tm.tmExternalLeading;

m_ncWidth = tm.tmAveCharWidth;

SetScrollSizes(MM_TEXT,GetDocument()->GetDocSize());

pDC->SelectObject(oldFont); // restore old font

CScrollView::OnInitialUpdate();

}

Page 131: 第七章  文档类对象持续性

4 修改 OnChar 函数void CEditorView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{ // TODO: Add your message handler code here and/or call default

CEditorDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CClientDC dc(this);

OnPrepareDC(&dc);

CFont *oldFont = dc.SelectObject(m_pFont);

TEXTMETRIC tm;

dc.GetTextMetrics(&tm);

CString line("");

// to get position of a string in text list rely on number of line

POSITION pos = pDoc->m_Lines.FindIndex(pDoc->m_nLineNum);

Page 132: 第七章  文档类对象持续性

if(nChar == '\r')

{ // to increase number of lines encountering a carriage return

if(!pos) // if there isn't the string line in the text list

pDoc->m_Lines.AddTail(CString(line)); // to add a empty lin

e

pDoc->m_nLineNum++;

}

else

{

if(!pos) // if there isn't the string line in the text list

{ // to add a new character into text buffer and add a new line

line += (char)nChar;

pDoc->m_Lines.AddTail(CString(line));

}

Page 133: 第七章  文档类对象持续性

else

{ // to add a new character into the current existed string line

line = pDoc->m_Lines.GetAt(pos);

line += (char)nChar;

pDoc->m_Lines.SetAt(pos, line);

}

dc.TextOut(0, (int)pDoc->m_nLineNum * tm.tmHeight,

line, line.GetLength());

}

pDoc->SetModifiedFlag(TRUE);

dc.SelectObject(oldFont);

SetScrollSizes(MM_TEXT, pDoc->GetDocSize());

CScrollView::OnChar(nChar, nRepCnt, nFlags);

}

Page 134: 第七章  文档类对象持续性

5 编译运行

程序运行的结果存在如下问题:

·滚动条的状态不随着文本的大小的变化而变化;

· 当输入的文本超出视图的客户区时,视图不随之发生滚动。

实例 “ Editor2” 对 Editor1 中的上述问题进行了修改,解决这些

问题的方法是进行如下的代码修改:

Page 135: 第七章  文档类对象持续性

① 在 CEditorDoc 类中添加最长文本行的字符数 m_nCharsInLine

属性,并用该属性和文本行数属性 m_nLineNum 替代原来描述文档尺寸的属性 m_sizeDoc 和成员函数 GetDocSize :class CEditorDoc : public CDocument

{

protected: // create from serialization only

CEditorDoc();

DECLARE_DYNCREATE(CEditorDoc)

public:

CStringList m_Lines; // the list for saving lines of string

int m_nLineNum; // the number of a specified string line

int m_nCharsInLine;// the maximum number of characters in a string line

}

Page 136: 第七章  文档类对象持续性

② 在 CEditorDoc 类构造函数中对 m_nCharsInLine 进行初始化:CEditorDoc::CEditorDoc()

{

// TODO: add one-time construction code here

m_nLineNum = m_nCharsInLine = 0;

}

Page 137: 第七章  文档类对象持续性

③ 修改 CEditorDoc 类的成员函数 DeleteContents :void CEditorDoc::DeleteContents()

{ // TODO: Add your specialized code here and/or call the base class

m_nLineNum = m_nCharsInLine = 0;

POSITION pos;

pos = m_Lines.GetHeadPosition();

while(pos != NULL)

((CString)m_Lines.GetNext(pos)).Empty();

// to empty a line of string

m_Lines.RemoveAll();

CDocument::DeleteContents();

}

Page 138: 第七章  文档类对象持续性

④ 修改 CEditorView 类的成员函数 OnInitialUpdate :void CEditorView::OnInitialUpdate()

{ // TODO: Add your specialized code here and/or call the base class

CDC *pDC = GetDC();

m_pFont = new CFont();

if(!m_pFont->CreateFont(0,0,0,0,FW_NORMAL,FALSE,FALSE,FALSE,

ANSI_CHARSET,OUT_TT_PRECIS,CLIP_TT_ALWAYS,

DEFAULT_QUALITY,DEFAULT_PITCH,"Courier New")))

{

m_pFont->CreateStockObject(SYSTEM_FONT);

}

CFont* oldFont = pDC->SelectObject(m_pFont);

// to select new font and save old font

Page 139: 第七章  文档类对象持续性

TEXTMETRIC tm;

pDC->GetTextMetrics(&tm);

m_nlHeight = tm.tmHeight + tm.tmExternalLeading;

m_ncWidth = tm.tmAveCharWidth;

CSize size(m_nlHeight * GetDocument()->m_nLineNum,

m_ncWidth * GetDocument()->m_nCharsInLine);

SetScrollSizes(MM_TEXT,size);

pDC->SelectObject(oldFont); // restore old font

CScrollView::OnInitialUpdate();

}

Page 140: 第七章  文档类对象持续性

⑤ 修改 CEditorView 类的成员函数 OnChar :void CEditorView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{ // TODO: Add your message handler code here and/or call default

CEditorDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CClientDC dc(this);

OnPrepareDC(&dc);

CFont *oldFont;

oldFont = dc.SelectObject(m_pFont);

CRect rect;

GetClientRect(&rect);

CPoint p;

CString line("");

static int nChars;

Page 141: 第七章  文档类对象持续性

// to get position of a string in text list rely on number of line

POSITION pos = pDoc->m_Lines.FindIndex(pDoc->m_nLineNum);

if(nChar == '\r')

{ // to increase number of lines encountering a carriage return

if(!pos) // if there isn't the string line in the text list.

pDoc->m_Lines.AddTail(CString(line)); // to add a empty lin

e

pDoc->m_nLineNum++;

nChars = 0;

// to modify the position for scrolling

p.x = 0;

p.y = max(0, m_nlHeight * pDoc->m_nLineNum - rect.Height() + 20);

}

else

{

Page 142: 第七章  文档类对象持续性

if(!pos)

{ // to add a new character into text buffer and add a new line i

nto

// the text list if there isn't the string line in the text list.

line += (char)nChar;

pDoc->m_Lines.AddTail(CString(line));

}

else

{ // to add a new character into the current existed string line

// in the text list.

line = pDoc->m_Lines.GetAt(pos);

line += (char)nChar;

pDoc->m_Lines.SetAt(pos, line);

}

Page 143: 第七章  文档类对象持续性

nChars++;

pDoc->m_nCharsInLine = max(pDoc->m_nCharsInLine, nChars);

dc.TextOut(0, (int)pDoc->m_nLineNum * m_nlHeight, line,

line.GetLength());

// to modify the position for scrolling

p.x = max(0, m_ncWidth * nChars - rect.Width() + 20);

p.y = max(0, m_nlHeight * pDoc->m_nLineNum - rect.Height() + 20);

}

// to scroll the view window rely on position p

pDoc->SetModifiedFlag(TRUE);

dc.SelectObject(oldFont);

CSize size(m_ncWidth * (pDoc->m_nCharsInLine + 1),

m_nlHeight * (pDoc->m_nLineNum + 1));

Page 144: 第七章  文档类对象持续性

SetScrollSizes(MM_TEXT, size);

ScrollToPosition(p);

CScrollView::OnChar(nChar, nRepCnt, nFlags);

}

上述文本编辑器,如果使用 CRichEditCtrl 或 CRichEditView ,

能够方便地构造出功能更加丰富的编辑器。实例 “ RichEditor”

是使用 CRichEditCtrl 构造的文档视图结构的文本编辑器,实例

“RichEditor1” 是使用 CRichEditView 构造的文档视图结构的文本

编辑器,而实例 “ RichEditor2” 是使用 CRichEditCtrl 构造的对话框

结构的文本编辑器。这些实例中实现的文本编辑器的功能

类似,但它们的实现和用途有所差异。