📄 13.4.2 利用可串行化类的 serialize函数保存和加载对象.txt
字号:
13.4.2 利用可串行化类的 Serialize函数保存和加载对象
下面,在 CGraphic View类的 OnLButtonUp函数中,当绘制图形之后,将图形要素保存起来,在前
面第 11章中使用集合类: CPtrList来实现,这电使用另一个类=CObArTay来实现,该类的用法与
CPtrList非常类似,只是在添加元素时添加的是 CO同 ect指针。因为 CGraph类就是从 CObject类
派生的,所以使用 CObArray类比较合适。为了保存多个图形要素,为 CGraphicView类添加一个
CObArray类型的成员变量 : ffi_obArray,并将其访问权限设置为 public类型,因为随后在文档类
中需要访问这个变量。
另外,为了简单起见,不再设置画笔的颜色,所以将此函数中己有的构造 CPen对象的代码,以及将
此对象选入设备描述表中的代码注释起来。接着,在绘制图形之后,添加如例 13-21所示代闺中加
灰显示的代码,先构造 CGraph对象,然后将该对象添加到集合类中。
例 13.21
void CGraphicView: :OnLButtonUp(UINT nFlags , CPoint point)
{
// TODO : Add your message handler code here and/or call default
CClientDC dc(this) ;
// CPen pen(m_nL工 neStyle, m_nLineWidth , m_clr) ;
// dc . SelectObject(&pen);
CBrush
*pBrush=CBrush: : FromHandle( (HBRUSH)GetStockObject(NULL_BRUSH)); dc .
SelectObject(pBrush) ; switch(m_nDrawType) {
case 1: dc.SetPixel(point , m_clr); break;
case 2: dC.MoveTo(m-ptOrigin) ; dc.LineTo(point) ; break;
case 3: dc.Rectangle(CRect(m-ptOrigin , point)) ; break;
case 4: dc.Ellipse(CRect(m-ptOrigin , point)) ; break;
CGraph *pGraph=new CGraph(m_nDrawType,m-ptOrigin, point); ( m_obArray.Add(pGraph) ;
Cview : :OnLButtonUp(nFlags , point);
另外,在 CGraphicView类中如果想要使用 CGraph类型的对象,那么必须将该对象定
义所在的头文件包含进来,因此,在 GraphicView.cpp文件的前部添加下面这条语句:
#include "Graph .h "
接下来,在 CGraphicDoc类的 Serialize函数中,将 CGraphicView对象的集合类对象: m_obArray
中保存的图形元素写入到文件中。首先我们需要将该函数中先前添加的代码注释起来。另外,在文
档类中想要访问视类的对象,首先就要获得视类对象的指针。我们知道,对于一个文档类对象来说,
可以有多个视类对象与之相关。但对于一个视类对象来说,它只能与一个文档类对象相关。因此,
为了获得与文档对象相关的视类对象,首先就要通过 CDocument类的 GetFirs tView Pos iti o n
成员函数获得与该文档对象相关的视类链表中第一个视类对象的位置,然后通过 GetNextView函数
得到当前位置所指示的视类对象指针。 CDocument类的 GetFirst ViewPosition成员函数的原型声
明如下所示 :
virtual POSITION GetFirstViewPosition( ) const ;
该函数将返回与文档相关联的视类对象链表中的第一个视类对象的位置,这个位置可以被
GetNextView函数选代使用。返回值类型是 POSOTION,这种类型被 MFC中的集合类用来表示集合中
元素的位置。
GetNextView函数的原型声明如下所示 :
virtual CView* GetNextView( POS工T工ON& rPosition ) const ;
可以看到, GetNextView函数有一个 POSITION类型的参数 : rPosition。当调用 GetFirstViewPo s
ition函数之后就可以获得第一个视类对象的位置,然后将这个值作为参数传递给 GetNextVie w函
数,该函数调用之后,将返回这个位置所标识的视类对象指针,然后通过 rPosition参数返回下一
个视类对象的位置。于是,通过不断地调用 GetNextVie w函数,就可以得到与文档类对象相关的每
一个视类对象。如果到了视类链表的末尾,即没有下一个视类对象了, rPo s ition的值就会被设
置为 NULL。因此程序中就可以根据这个条件,终止调用 GetNextView函数。通过这种方法可以迭代
地访问与文档对象相关的每一个视类对象。因为本程序是一个单文档类型的程序,所以它只有一个
视类对象。所以在 CGraphicDoc类的 Serialize函数中开始位置添加如例 13-22所示代码中加灰显
示的代码。
例 13 -22
void CGraphicDoc : : Serialize(CArchive& ar)
POSiTION pos=GetFirstViewPosition() ;
CGraphicView *pView= (CGraphicView*)GetNextView(pos) ;
if (ar . IsStoring())
// TODO: add storing code here
inti=5 ;
char ch='b' ;
float f =1. 2f;
CString str ( "http://www . sunxin. org") ;
ar <<i <<ch<<f<<str;
else
11 T ODO : add lo ad工 ng code here
int i:
char ch:
float f:
CStrlng str:
CString strResult:
a r>>i>>ch>>f>>str:
strResult .Forrnat( "屯d,毡c,毡f.毡s ", i , ch , f. str) :
Af xMessageBox (strResult) :
上述如例 13-22所示代码通过调用 GetFirstViewPosition函数和 GetNextView函数获取与文档对
象相关的第一个视类对象的指针,也就是 CGraphicView对象的指针。要注意的是. GetNextView函
数返回的是 CView*.而我们需要的是 CGraphicView飞因此需要进行一个强制类型转换。
有了视类对象指针,就可以利用此指针访问视类对象的成员变量了。于是在 CGraphicDoc类的
Serialize函数中,在保存数据时,得到 CGraphicView对象的 ID_obArray这个集合类对象中保存的
对象数目,然后利用一个 for循环结构遍历这些元素,分别将它们保存到文件中。但是,如果 for
循环语句用下面这条代码来实现:
for(int i=0;i<pView->m_obArray.GetSize();i++)
那么,每次循环时都会调用 GetSize函数去求取集合类对象中元素的数目,这样会影
响程序的执行效率。我们在编写代码时,应该注意代码优化问题。这里,我们可以把 GetSize这个
函数调用提取出来,单独定义一个变量来保存该函数调用后得到的集合类对象元素数目。如果要提
取己保存到文件中的数据,首先需要知道该文件中保存的对象数目。所以在保存集合类对象所保存
的图形对象时,可以在保存具体的对象数据之前,先将所要保存的对象数目也保存到文件中。接下
来,在 for循环内部,利用 CArchive对象保存集合类对象中所保存的图形对象。因为 CGraph类本
身支持串行化,所以可以直接保存该类对象。于是,我们可以在例 13-22所示 CGraphicDoc类
Serialize函数的 if语句块中添加下述代码。
int nCount=pView->m_obArray.GetSize();
ar<<nCount;
for(int i=0;i<nCount;i++)
ar<<pView->m_obArray.GetAt(i) ;
在读取对象数据时,可以先取出对象数据,然后,也利用 for循环语句来读取所有对象的数据。每
次取出一个 CGraph对象之后,都可以将这个对象的地址加入到 CGraphic View 对象的 ID_obArray
这个集合类对象中。于是,我们可以在如例 13-22所示 CGraphicDoc类
Serialize函数的else分支下添加下述代码:
int nCount;
ar>>nCount;
CGraph *pGraph;
for(int i=0;i<nCount;i++)
ar>>pGraph;
pView->rn_obArray.Add(pGraph) ;
注意,这里并没有先构造一个CGraph对象,再读取其数据。因为这里在利用CArchive类重载的提取
操作符 C>>)读取对象时,它会自动调用CGraph类的不带参数的构造函数去构造相应对象,然后将这
个对象的地址赋给 pGraph这个指针变量。所以这里并不需要
为该指针变量分配内存空间。
在CGraphicDoc类中若想要访问CGraphicView类和CGraph类,需要包含相应的头文件,即在
GraphicDoc.cpp文件的前部添加下述语句:
#include "GraphicView.h"
#include "Graph.h"
在提取文件数据时,当我们将CGraph对象添加到集合类对象之后,还需要将这些图形在程序窗口中
显示出来。这可以在 CGraphicView类的 OnDraw函数中把图形的重绘工作完成了。因为在 CGraph
类中已经包含了图形绘制函数 (Draw),所以这里只需要调用这个函数就可以。于是,在CGraphicView
类的OnDraw函数中添加如例 13-23所示代码中加灰显示的代码。
例13-23
void CGraphicView: : OnDraw(CDC食 pDC)
{ CGraphicDoc* pDoc = GetDocurnent(); ASSERT_VAL工o (pDoc) ; // TODO: add draw code for
native data here
/ * CFont *pOldFont=pDC->SelectObject(&rn_font);
pDC->TextOut(Q , Q, rn_strFontNarne) ;
pDC->SelectObject(pOldFont) ;
*/
int nCount;
nCount=m_obArray.GetSize() ;
for(int i=0 ,i<nCount ,i++)
{
((CGraph*)m_obArray.GetAt(i))->Draw(pDC) ;
新添加的代码通过调用集合类对象的GetAt函数获取元素,并需要将结果强制转换为 CGrath*类型,
然后利用此指针调用 CGraph对象的 Draw函数完成图形的绘制。而调用
" I 513
第 13章文档与串行化
Draw函数时,需要传递一个 CDC*类型的参数,正好 CGraphicView类的 OnDraw函数中
有一个 CDC指针类型的变量 : pDC,因此直接把这个变量作为参数传递给 Draw函数就可
以了。
运行 Graphic程序,先选择相关绘图菜单,在窗口中绘制一些图形。然后选择【文件\保存】菜单项,
把图形对象保存到一个指定的文件中,例如 Graphic.txt。然后关闭程序,再次运行,然后选择【文
件\打开】菜单项,并选择 Graphic.txt文件打开。这时就可以从该文件中加载数据,并在内存中重
新构造 CGraph对象,随即就可以看到在程序窗口中显示出先前绘制的图形了。以上就是利用 C
Archive类和可串行化类的 Serialize函数保存对象和加载对象的实现。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -