文档与序列化
一、文档的基本特征
文档类文件是从CDocument继承而来的。
The CDocument class provides the basic functionality for user-defined document classes. A document represents the unit of data that the user typically opens with the File Open command and saves with the File Save command.
翻译:文档类提供用户自定义文档类的基本功能。一个文档的打开命令和保存命令是数据单元特征。
CDocument
supports standard operations such as creating a document, loading it, and saving it. The framework manipulates documents using the interface defined by CDocument.
翻译:文档类支持标准操作有:创建文档,载入文档,和保存。框架界面使用由文档类定义的界面。
An application can support more than one type of document; for example, an application might support both spreadsheets and text documents. Each type of document has an associated document template; the document template specifies what resources (for example, menu, icon, or accelerator table) are used for that type of document. Each document contains a pointer to its associated CDocTemplate object.
翻译:一个应用程序可以支持多于一种类型的文档;例如,一个应用程序可能同时支持电子表格和普通文本文档。每一种类型的文档有一种关联文档模板;文档模板指定了(例如,菜单,图标,或者加速表格)使用这些类型的文档。每个文档包含一个指针指向关联的模板对象。
Users interact with a document through the
CView
object(s) associated with it. A view renders an image of the document in a frame window and interprets user input as operations on the document. A document can have multiple views associated with it. When the user opens a window on a document, the framework creates a view and attaches it to the document. The document template specifies what type of view and frame window are used to display each type of document.
翻译:用户将一个文档和一个视类对象想关联起来。一个视类扮演一个嵌入在文档框架内的图像,在文档内解释用户的输入操作。一个文档可以有多个视类与其关联。当用户在一个文档上打开一个窗体,框架创造一个视图,把它放在文档上。文档模板指定适合于用户文档类型的相同类的视图和框架窗口用来显示用户打开的文件。
Documents are part of the framework's standard command routing and consequently receive commands from standard user-interface components (such as the File Save menu item). A document receives commands forwarded by the active view. If the document doesn't handle a given command, it forwards the command to the document template that manages it.
翻译:文档是一个框架标准命令路由,因此从标准用户界面组件(如文件保存菜单项)接受命令。一个文档由其活动视图迅速接受命令。如果该文档没有没有响应给出的命令,那么该命令将交由文档模板来管理。
When a document's data is modified, each of its views must reflect those modifications. CDocument provides the
UpdateAllViews
member function for you to notify the views of such changes, so the views can repaint themselves as necessary. The framework also prompts the user to save a modified file before closing it.
翻译:当一个文档数据发生改变,每一个它的视图将随即发生改变。文档类提供更新所有视图成员函数来通报每一个视图的改变,因此视图可以在必要的时候被重绘。框架也在关闭前迅速保存改动文件。
To implement documents in a typical application, you must do the following:
l
Derive a class from CDocument for each type of document.
l
Add member variables to store each document's data.
l
Implement member functions for reading and modifying the document's data. The document's views are the most important users of these member functions.
l
Override the CObject::Serialize member function in your document class to write and read the document's data to and from disk.
翻译:在一个典型类型的应用程序中实现一个文档,你必须按以下步骤:
l
为每一种类型的文档从
CDocument
派生一个类
l
增加成员函数来存储每一个文档的数据
l
为读写文档数据实现成员函数。文档视图类是这个成员函数最重要的使用者。
l
在你的文档类重载
CObject::Serialize
成员函数,来将文档数据从磁盘读写。
序列化:
本文解释
Microsoft
基础类库
(MFC)
中提供的序列化机制,该机制使对象可以在程序运行之间保持。
序列化是指将对象写入永久性存储媒体(如磁盘文件)或从其中读取对象的进程。
MFC
对
CObject
类中的序列化提供内置支持。因此,所有从
CObject
派生的类都可利用
CObject
的序列化协议。
序列化的基本思想是对象应能将其当前状态(通常由该对象的成员变量指示)写入永久性存储中。以后,通过从存储中读取对象状态或反序列化对象状态,可以重新创建该对象。序列化处理序列化对象时使用的对象指针和对象循环引用的所有详细资料。关键之处在于对象本身负责读写其自身状态。因此,对于可序列化的类,必须实现基本的序列化操作。正如
“
序列化
”
文章组中所显示的那样,很容易将该功能添加到类中。
MFC
将
CArchive
类的对象用作将被序列化的对象和存储媒体之间的中介物。该对象始终与
CFile
对象相关联,它从
CFile
对象获得序列化所需的信息,包括文件名和请求的操作是读取还是写入。执行序列化操作的对象可在不考虑存储媒体本质的情况下使用
CArchive
对象。
CArchive
对象使用重载输出运算符
(<<)
和输入运算符
(>>)
来执行读写操作。有关更多信息,请参见
“
序列化:序列化对象
”
文章中的
通过存档存储和加载
CObjects
。
注意
请不要将
CArchive
类与通用
iostream
类混淆,
iostream
类只用于格式化的文本。而
CArchive
类则用于二进制格式的序列化对象。
如果愿意,可以不使用
MFC
序列化而为永久性数据存储创建自己的机制。您将需要在用户的命令处重写启动序列化的类成员函数。请参见
ID_FILE_OPEN
、
ID_FILE_SAVE
及
ID_FILE_SAVE_AS
标准命令的
技术说明
22
中的讨论。
下列文章介绍了序列化所需的两个主要任务:
l
序列化:创建可序列化的类
l
序列化:序列化对象
序列化:序列化与数据库输入
/输出的对比
一文描述了序列化在数据库应用程序中何时是适当的输入
/
输出技术。
通过存档存储和加载 CObjects
通过存档存储及加载
CObject
需要额外注意。在某些情况下,应调用对象的
Serialize
函数,其中,
CArchive
对象是
Serialize
调用的参数,与使用
CArchive
的
“<<”
或
“>>”
运算符不同。要牢记的重要事实是:
CArchive
的
“>>”
运算符基于由存档先前写到文件的
CRuntimeClass
信息构造内存中的
CObject
。
因此,是使用
CArchive
的
“<<”
和
“>>”
运算符还是调用
Serialize
,取决于是否需要加载存档基于先前存储的
CRuntimeClass
信息动态地重新构造对象。在下列情况下使用
Serialize
函数:
l
反序列化对象时,预先知道对象的确切的类。
l
反序列化对象时,已为其分配了内存。
警告
如果使用
Serialize
函数加载对象,也必须使用
Serialize
函数存储对象。不要使用
CArchive
的
“<<”
运算符先存储,然后使用
Serialize
函数进行加载;或使用
Serialize
函数存储,然后使用
CArchive
的
“>>”
运算符进行加载。
以下示例阐释了这些情况:
class CMyObject : public CObject
{
// ...Member functions
public:
CMyObject() { }
virtual void Serialize( CArchive& ar ) { }
// Implementation
protected:
DECLARE_SERIAL( CMyObject )
};
class COtherObject : public CObject
{
// ...Member functions
public:
COtherObject() { }
virtual void Serialize( CArchive& ar ) { }
// Implementation
protected:
DECLARE_SERIAL( COtherObject )
};
class CCompoundObject : public CObject
{
// ...Member functions
public:
CCompoundObject();
virtual void Serialize( CArchive& ar );
// Implementation
protected:
CMyObject m_myob; // Embedded object
COtherObject* m_pOther; // Object allocated in constructor
CObject* m_pObDyn; // Dynamically allocated object
//..Other member data and implementation
DECLARE_SERIAL( CCompoundObject )
};
IMPLEMENT_SERIAL(CMyObject,CObject,1)
IMPLEMENT_SERIAL(COtherObject,CObject,1)
IMPLEMENT_SERIAL(CCompoundObject,CObject,1)
CCompoundObject::CCompoundObject()
{
m_pOther = new COtherObject; // Exact type known and object already
//allocated.
m_pObDyn = NULL; // Will be allocated in another member function
// if needed, could be a derived class object.
}
void CCompoundObject::Serialize( CArchive& ar )
{
CObject::Serialize( ar ); // Always call base class Serialize.
m_myob.Serialize( ar ); // Call Serialize on embedded member.
m_pOther->Serialize( ar ); // Call Serialize on objects of known exact type.
// Serialize dynamic members and other raw data
if ( ar.IsStoring() )
{
ar << m_pObDyn;
// Store other members
}
else
{
ar >> m_pObDyn; // Polymorphic reconstruction of persistent
// object
//load other members
}
}
总之,如果可序列化的类将嵌入的
CObject
定义为成员,则不应使用该对象的
CArchive
的
“<<”
和
“>>”
运算符,而应调用
Serialize
函数。同时,如果可序列化的类将指向
CObject
(或从
CObject
派生的对象)的指针定义为成员,但在自己的构造函数中将其构造为其他对象,则也应调用
Serialize
。
序列化:创建可序列化的类
ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/vccore/html/_core_serialization.3a_.making_a_serializable_class.htm
使类可序列化需要五个主要步骤。下面列出了这些步骤并在以后章节内进行了解释:
-
从
CObject 派生类
(或从
CObject
派生的某个类中派生)。
-
重写
Serialize 成员函数
。
-
使用
DECLARE_SERIAL 宏
(在类声明中)。
-
定义不带参数的构造函数
。
-
为类
在实现文件中使用
IMPLEMENT_SERIAL 宏
。
如果直接调用 Serialize而不是通过 CArchive的“>>”和“<<”运算符调用,则序列化不需要最后三个步骤。
从
CObject
派生类
在
CObject
类中定义了基本的序列化协议和功能。正如在
CPerson
类的下列声明中所示,通过从
CObject
中(或从
CObject
的派生类中)派生类,可获得对
CObject
的序列化协议及功能的访问权限。
重写
Serialize
成员函数
在
CObject
类中定义的
Serialize
成员函数实际上负责对捕获对象的当前状态所必需的数据进行序列化。
Serialize
函数具有
CArchive
参数,该函数使用其来读写对象数据。
CArchive
对象具有成员函数
IsStoring
,该成员函数指示
Serialize
正在存储(即正在写入数据)还是正在加载(即正在读取数据)。用
IsStoring
的结果作为参考,使用输出运算符
(<<)
将对象数据插入到
CArchive
对象中或使用输入运算符
(>>)
提取数据。
假定一个类是从
CObject
派生的并具有两个新成员变量,分别为
CString
和
WORD
类型。下列类声明段显示了新成员变量和重写的
Serialize
成员函数的声明:
class CPerson : public CObject
{
public:
DECLARE_SERIAL( CPerson )
// empty constructor is necessary
CPerson(){};
CString m_name;
WORD m_number;
void Serialize( CArchive& archive );
// rest of class declaration
};
重写
Serialize
成员函数
-
调用
Serialize
的基类版本以确保序列化对象的继承部分。
-
插入或提取您的类所特定的成员变量。
输出运算符及输入运算符与存档类交互作用以读写数据。下面的示例显示了如何实现以上声明的 CPerson
类的 Serialize:
void
CPerson::Serialize( CArchive
&
archive )
{
//
call base class function first
//
base class is CObject in this case
CObject::Serialize( archive );
//
now do the stuff for our specific class
if
( archive.IsStoring() )
archive
<<
m_name
<<
m_number;
else
archive
>>
m_name
>>
m_number;
}
也可使用 CArchive::Read及 CArchive::Write成员函数来读写大量未键入的数据。
使用
DECLARE_SERIAL
宏
在支持序列化的类的声明中需要 DECLARE_SERIAL宏,如下所示:
class
CPerson :
public
CObject
{
DECLARE_SERIAL( CPerson )
//
rest of declaration follows
}
;
定义不带参数的构造函数
反序列化对象(从磁盘上加载)后,MFC 重新创建这些对象时,需要一个默认的构造函数。反序列化进程将用重新创建对象所需的值填充所有成员变量。
可将该构造函数声明为公共的、受保护的或私有的。如果使该构造函数成为受保护的或私有的,请确保它将仅由序列化函数使用。该构造函数必须使对象处于这样一种状态:必要时,可允许将其安全删除。
注意
如果忘记在使用
DECLARE_SERIAL
及
IMPLEMENT_SERIAL
宏的类中定义不带参数的构造函数,将在使用
IMPLEMENT_SERIAL
宏的行上得到
“
没有可用的默认构造函数
”
编译器警告。
在实现文件中使用
IMPLEMENT_SERIAL
宏
IMPLEMENT_SERIAL
宏用于定义从 CObject中派生可序列化类时所需的各种函数。在类的实现文件 (.CPP) 中使用这个宏。该宏的前两个参数是类名和直接基类的名称。
该宏的第三个参数是架构编号。架构编号实质上是类对象的版本号。架构编号使用大于或等于零的整数。(请不要将该架构编号与数据库术语混淆。)
MFC
序列化代码在将对象读取到内存时检查该架构编号。如果磁盘上对象的架构编号与内存中类的架构编号不匹配,库将引发 CArchiveException,防止程序读取对象的不正确版本。
如果要使 Serialize
成员函数能够读取多个版本(即,读取用应用程序的不同版本写入的文件),可将 VERSIONABLE_SCHEMA值作为 IMPLEMENT_SERIAL宏的参数。有关用法信息和示例,请参见 CArchive类的 GetObjectSchema成员函数。
以下示例显示了如何将 IMPLEMENT_SERIAL用于从 CObject派生的 CPerson
类。
IMPLEMENT_SERIAL( CPerson, CObject, 1 )
正如序列化:序列化对象文章中所讨论的,一旦具有可序列化的类,就可以序列化类的对象。
序列化 :序列化对象
序列化:创建可序列化的类
一文说明如何使类可序列化。一旦具有可序列化的类,就可以通过 CArchive对象将该类的对象序列化到文件和从文件序列化该类的对象。本文将解释:
可使框架创建可序列化文档的存档或自己显式创建 CArchive对象。通过使用 CArchive的“<<”和“>>”运算符或在某些情况下通过调用 CObject派生类的 Serialize 函数,可在文件和可序列化对象间传输数据。
什么是Archive对象?
CArchive
对象提供了一个类型安全缓冲机制,用于将可序列化对象写入 CFile对象或从中读取可序列化对象。通常,CFile对象表示磁盘文件;但是,它也可以是表示“剪贴板”的内存文件(CSharedFile对象)。
给定的 CArchive对象要么存储数据(即写入数据或将数据序列化),要么加载数据(即读取数据或将数据反序列化),但决不能同时进行。CArchive对象的寿命只限于将对象写入文件或从文件读取对象的一次传递。因此,需要两个连续创建的 CArchive对象将数据序列化到文件,然后从文件反序列化数据。
当存档将对象存储到文件时,存档将 CRuntimeClass名称附加到这些对象。然后,当另一个存档将对象从文件加载到内存时,将基于这些对象的 CRuntimeClass动态地重新构造 CObject派生的对象。通过存储存档将给定对象写入文件时,该对象可能被引用多次。然而,加载存档将仅对该对象重新构造一次。有关存档如何将 CRuntimeClass信息附加到对象以及重新构造对象(考虑可能的多次引用)的详细信息,请参见技术说明 2
。
将数据序列化到存档时,存档积累数据,直到其缓冲区被填满为止。然后,存档将其缓冲区写入 CArchive对象指向的 CFile对象。同样,当您从存档中读取数据时,存档会将数据从文件读取到它的缓冲区,然后从缓冲区读取到反序列化的对象。这种缓冲减少了物理读取硬盘的次数,从而提高了应用程序的性能。
创建 CArchive 对象有两种方法:
l
通过框架隐式创建 CArchive 对象
l
显式创建 CArchive 对象
通过框架隐式创建
CArchive
对象
最普通且最容易的方法是使框架代表“文件”菜单上的“保存”、“另存为”和“打开”命令为文档创建
CArchive
对象。
以下是应用程序的用户从“文件”菜单上发出“另存为”命令时,框架所执行的操作:
显示“另存为”对话框并从用户获取文件名。
打开用户命名的文件作为
CFile
对象。
创建指向该
CFile
对象的
CArchive
对象。在创建
CArchive
对象时,框架将模式设置为“存储”(即写入或序列化),而不是“加载”(即读取或反序列化)。
调用在
CDocument
派生类中定义的
Serialize
函数,将
CArchive
对象的引用传递给该函数。
然后,文档的
Serialize
函数将数据写入
CArchive
对象(刚作了解释)。从
Serialize
函数返回时,框架先销毁
CArchive
对象,再销毁
CFile
对象。
因此,如果让框架为文档创建
CArchive
对象,您所要做的一切是实现写入存档和从存档中读取的文档的
Serialize
函数。您还必须为文档的
Serialize
函数直接或间接依次序列化的任何
CObject
派生对象实现
Serialize
。
显式创建
CArchive
对象
除了通过框架将文档序列化之外,在其他场合也可能需要
CArchive
对象。例如,可能要序列化到达或来自剪贴板的数据,由
CSharedFile
对象表示。或者,可能要使用用户界面来保存与框架提供的文件不同的文件。在这种情况下,可以显式创建
CArchive
对象。使用下列过程,用与框架采用的相同方式来执行此操作。
显式创建
CArchive
对象
构造
CFile
对象或从
CFile
导出的对象。
按照下面示例所示,将
CFile
对象传递到
CArchive
的构造函数:
CFile theFile;
theFile.Open(..., CFile::modeWrite);
CArchive archive(&theFile, CArchive::store);
CArchive
构造函数的第二个参数是指定存档将用于向文件中存储数据还是用于从文件中加载数据的枚举值。对象的
Serialize
函数通过调用存档对象的
IsStoring
函数来检查该状态。
当完成向
CArchive
对象存储数据或从该对象中加载数据时,关闭该对象。虽然
CArchive
对象(和
CFile
对象)会自动关闭存档(和文件),好的做法是显式执行,因为这使从错误恢复更为容易。有关错误处理的更多信息,请参见异常:捕捉和删除异常一文。
关闭
CArchive
对象
以下示例阐释了如何关闭
CArchive
对象:
archive.Close();
theFile.Close();
使用
CArchive
对象的“
<<
”和“
>>
”操作符
CArchive
提供“
<<
”和“
>>
”运算符,用于向文件中写入简单的数据类型和
CObjects
以及从文件中读取它们。
通过存档将对象存储在文件中
以下示例显示了如何通过存档将对象存储在文件中:
CArchive ar(&theFile, CArchive::store);
WORD wEmployeeID;
...
ar << wEmployeeID;
从先前存储在文件中的值加载对象
以下示例显示了如何从先前存储在文件中的值加载对象:
CArchive ar(&theFile, CArchive::load);
WORD wEmployeeID;
...
ar >> wEmployeeID;
通常,通过
CObject
派生类的
Serialize
函数中的存档将数据存储到文件中或从文件中加载数据,必须已用
DECLARE_SERIALIZE
宏来声明这些函数。将
CArchive
对象的引用传递到
Serialize
函数。调用
CArchive
对象的
IsLoading
函数以确定是否已调用
Serialize
函数来从文件中加载数据或将数据存储到文件中。
可序列化的
CObject
派生类的
Serialize
函数通常具有以下形式:
void CPerson::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}
上面的代码模板与
AppWizard
为该文档(从
CDocument
派生的类)的
Serialize
函数所创建的代码模板完全相同。由于存储代码和加载代码总是并行,该代码模板有助于写的代码更容易复查,如下例中所示:
void CPerson:Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << m_strName;
ar << m_wAge;
}
else
{
ar >> m_strName;
ar >> m_wAge;
}
}
库将
CArchive
的“
<<
”和“
>>
”运算符定义为第一操作数,将下列数据类型和类类型定义为第二操作数:
CObject*
|
SIZE
和 CSize
|
float
|
WORD
|
CString
|
POINT
和 CPoint
|
DWORD
|
BYTE
|
RECT
和 CRect
|
double
|
LONG
|
CTime
和 CTimeSpan
|
int
|
COleCurrency
|
COleVariant
|
COleDateTime
|
COleDateTimeSpan
|
|
注意
通过存档存储及加载
CObjects
需要额外注意。有关更多信息,请参见通过存档存储和加载
CObjects
。
CArchive
的“
<<
”和“
>>
”运算符总是返回
CArchive
对象的引用,该引用为第一操作数。这使您可以链接运算符,如下所示:
BYTE bSomeByte;
WORD wSomeWord;
DWORD wSomeDoubleWord;
...
ar << bSomeByte << wSomeWord << wSomeDoubleWord;
通过存档存储及加载 CObject
(见前)
下面用一个示例来解释这个问题。
目标:一个画图程序,通过保存打开按钮存取图片。方法:保存图片绘制信息。
按步骤:
l
创建可序列化的类
->Graph.cpp+Graph.h
l
在
View
类中添加对控件的响应,实现画图功能,每次鼠标弹起的时候保存绘图信息
l
保存文件(通过
Doc
的
Serialize
来保存数据)
l
打开文件(通过
Doc
的
Serialize
来读取数据,并将其重绘)
在
View
类中定义
CObArray m_obArray;
下面按这个思路来完成:(在
C
Graph
子类中完成重绘的画图功能)
Graph.cpp
#include
"StdAfx.h"
#include
".\graph.h"
IMPLEMENT_SERIAL(CGraph, CObject, 1 )
CGraph::CGraph(void)
: m_ptOrigin(0)
, m_ptEnd(0)
, m_nDrawType(0)
{
}
CGraph::CGraph(CPoint m_ptOrigin,CPoint m_ptEnd,UINT m_nDrawType)
{
this->m_ptOrigin=m_ptOrigin;
this->m_ptEnd=m_ptEnd;
this->m_nDrawType=m_nDrawType;
}
CGraph::~CGraph(void)
{
}
void
CGraph::Serialize( CArchive& ar )
{
//
继承基类的CObject
CObject::Serialize( ar );
if( ar.IsStoring() )
{
ar << m_ptOrigin << m_ptEnd << m_nDrawType ;
}
else
{
ar >> m_ptOrigin >> m_ptEnd >> m_nDrawType ;
}
}
void
CGraph::Draw(CDC* pDC)
{
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
CBrush *pOldBrush=pDC->SelectObject(pBrush);
switch(m_nDrawType)
{
case 1:
pDC->SetPixel(m_ptEnd,RGB(0,0,0));
break;
case 2:
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(m_ptEnd);
break;
case 3:
pDC->Rectangle(CRect(m_ptOrigin,m_ptEnd));
break;
case 4:
pDC->Ellipse(CRect(m_ptOrigin,m_ptEnd));
break;
}
pDC->SelectObject(pOldBrush);
}
Graph..h
#pragma
once
#include
"atltypes.h"
class
CGraph : publicCObject
{
public
:
DECLARE_SERIAL( CGraph )
CGraph(void);
CGraph::CGraph(CPoint m_ptOrigin,CPoint m_ptEnd,UINT m_nDrawType);
void Serialize( CArchive& ar );
~CGraph(void);
CPoint m_ptOrigin;
CPoint m_ptEnd;
UINT m_nDrawType;
void Draw(CDC* pDC);
};
在
View
类中添加画图功能
void
CGraphicSerialView::OnDot()
{
// TODO:
在此添加命令处理程序代码
m_nDrawType=1;
}
void
CGraphicSerialView::OnLine()
{
// TODO:
在此添加命令处理程序代码
m_nDrawType=2;
}
void
CGraphicSerialView::OnRectangle()
{
// TODO:
在此添加命令处理程序代码
m_nDrawType=3;
}
void
CGraphicSerialView::OnEllipse()
{
// TODO:
在此添加命令处理程序代码
m_nDrawType=4;
}
void
CGraphicSerialView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO:
在此添加消息处理程序代码和/或调用默认值
m_ptOrigin = point;
CView::OnLButtonDown(nFlags, point);
}
void
CGraphicSerialView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO:
在此添加消息处理程序代码和/或调用默认值
CClientDC dc(this);
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); //
选择一个透明画刷
dc.SelectObject(pBrush);
switch(m_nDrawType)
{
case 1:
dc.SetPixel(point,RGB(0,0,0));
break;
case 2:
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
break;
case 3:
dc.Rectangle(m_ptOrigin.x,m_ptOrigin.y,point.x,point.y);
break;
case 4:
dc.Ellipse(CRect(m_ptOrigin,point));
break;
default:
break;
}
//
将图形信息保存起来
CGraph *pGraph=new CGraph( m_ptOrigin , point , m_nDrawType ); //
创建一个pGraph指针指向一个存储图形信息的“图形”
m_obArray.Add(pGraph);//
将一群这样的pGraph组织在一起放进m_obArray
CView::OnLButtonUp(nFlags, point);
}
但是要保存和读取文件需要用到
Serialize
因此转到
Doc
类
void
CGraphicSerialDoc::Serialize(CArchive& ar)
{
POSITION pos=GetFirstViewPosition();//
获得第一个视类对象在对象列表中的位置
CGraphicSerialView *pView=(CGraphicSerialView*)GetNextView(pos);//
找到当前视类对象的指针,由于这是单文档,因此只有一个视类对象
if (ar.IsStoring())
{
// TODO:
在此添加存储代码
int nCount=pView->m_obArray.GetSize();
ar<<nCount; //
为了在读取文件的时候能够知道元素个数,因此在保存的时候把个数也存进去
for(int i=0;i<nCount;i++)
{
ar<<pView->m_obArray.GetAt(i);
}
}
else
{
// TODO:
在此添加加载代码
int nCount;
ar>>nCount;
CGraph *pGraph;
for(int i=0;i<nCount;i++)
{
ar>>pGraph;
pView->m_obArray.Add(pGraph);
}
}
}
但是读取数据的时候还需要重绘图像:转回View类,因为在View类加载的时候会自动调用OnDraw(),在OnDraw()中添加如下语句。
int nCount;
nCount=m_obArray.GetSize();
for(int i=0;i<nCount;i++)
{
((CGraph*)m_obArray.GetAt(i))->Draw(pDC);
}
基本上完成了通过serialize保存和打开文件。
用serialize的好处:通过缓存来保存,当缓存区满了才进行一次读/写,因此提高了应用程序的效率
另外
,
Archive
对象是支持序列化的,因此在读写数据的时候即可以按以上方法在
Doc
类的
Serialize
来保存和打开数据。
因此修改
Doc
中的
Serialize
为
void
CGraphicSerialDoc::Serialize(CArchive& ar)
{
POSITION pos=GetFirstViewPosition();//
获得第一个视类对象在对象列表中的位置
CGraphicSerialView *pView=(CGraphicSerialView*)GetNextView(pos);//
找到当前视类对象的指针,由于这是单文档,因此只有一个视类对象
if (ar.IsStoring())
{
// TODO:
在此添加存储代码
}
else
{
// TODO:
在此添加加载代码
}
pView->m_obArray.Serialize(ar); //
将这个数据传递给
}
其中CObArray类对象读取数据的方法:(以下是MFC源代码,无须用户添加)
void
CObArray::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
CObject::Serialize(ar);
if (ar.IsStoring())
{
ar.WriteCount(m_nSize);
for (INT_PTR i = 0; i < m_nSize; i++)
ar << m_pData[i];
}
else
{
DWORD_PTR nOldSize = ar.ReadCount();
SetSize(nOldSize);
for (INT_PTR i = 0; i < m_nSize; i++)
ar >> m_pData[i];
}
}
下面直接在
Doc
类中使用
CObArray
对象(取消之前定义在
View
类中的该类对象)
在
Doc
类中定义
CObArray m_obArray;
存图形数据的代码修改为:
CGraphicDoc *pDoc=GetDocument();//
通过视类提供的GetDocument()方法来获得Doc类指针
pDoc->m_obArray.Add(pGraph);
修改其他位置的
m_obArray
void
CGraphicSerialView::OnDraw(CDC* pDC)
{
……
nCount=pDoc->m_obArray.GetSize();
for(int i=0;i<nCount;i++)
{
((CGraph*)pDoc->m_obArray.GetAt(i))->Draw(pDC); //Doc类中定义m_obArray的情况
}
}
void
CGraphicSerialDoc::Serialize(CArchive& ar)
{
……
m_obArray.Serialize(ar);
//Doc
类中定义m_obArray的情况
}
当我们新建和打开文档的时候,我们之前的文档对象并没有被销毁(系统利用
OnNewDocument
方法所新建的对象)之前在堆上建立了文档对象。此时应删除文档对象。
在文档类中重载函数
void
CGraphicSerialDoc::DeleteContents()
来删除文档对象,以避免内存泄露。
void
CGraphicSerialDoc::DeleteContents()
{
int nCount;
nCount=m_obArray.GetSize();
/*for(int i=0;i<nCount;i++)
{
delete m_obArray.GetAt(i);
//m_obArray.RemoveAt(i);
}
m_obArray.RemoveAll();*/
while(nCount--)
{
delete m_obArray.GetAt(nCount);
m_obArray.RemoveAt(nCount);
}
CDocument::DeleteContents();
}
下面是关于内存泄露的在此的一段论述。(此文同时发布于)
http://www.cppblog.com/mymsdn/archive/2006/08/16/11266.html