13.4可串行化类

13.4.1实现类对串行化的支持

如果要用CArchive类保存对象的话,那么这个对象的类必须支持串行化。一个可串行化的类通常有一个Serialize成员函数。要想使一个类可串行化,要经历以下5个步骤:
1、从CObject派生类
2、重写Serialize成员函数
3、使用DECLARE_SERIAL宏:
DECLARE_SERIAL( class_name )参数就是想要成为可串行化类的类名。
4、定义不带参数的构造函数
5、为类在实现文件中使用IMPLEMENT_SERIAL宏:
IMPLEMENT_SERIAL( class_name, base_class_name, wSchema )
第一个参数是类的名称;第二个参数是基类的名称;第三个参数是版本号。

为了使用CArchive对象保存CGraph对象,首先要使CGraph类成为一个可串行化的类:
1、修改CGraph类的定义,让其派生于CObject。即在CGraph头文件中将该类定义修改为:class CGraph:public CObject
2、重载Serialize函数:
在CGraph头文件中增加该函数的声明:

class CGraph:public CObject
{
public:
 CPoint m_ptOrigin;
 CPoint m_ptEnd;
 UINT m_nDrawType;
 CGraph();
 CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd);
 void Serialize(CArchive& archive);
 virtual ~CGraph();
};

在源文件中实现该函数:

void CGraph::Serialize(CArchive& ar)
{
 if(ar.IsStoring())
 {
  ar<<m_nDrawType<<m_ptOrigin<<m_ptEnd;
 }
 else
 {
  ar>>m_nDrawType>>m_ptOrigin>>m_ptEnd;
 }
}

3、在CGraph类定义的内部添加宏调用的代码:
DECLARE_SERIAL(CGraph)
4、由于CGraph类定义了一个不带参数的构造函数,这里不需要重新定义
5、在CGraph.cpp中,在CGraph类构造函数前面添加宏调用的代码:
IMPLEMENT_SERIAL(CGraph,CObject,1)

经过以上几个步骤,CGraph类就支持串行化了,然后为此类在增加一个图形绘制函数:Draw,与添加Serialize成员函数一样,从而将图形数据和图形绘制封装在一个类中。

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);
}

13.4.2利用可串行化类的Serialize函数保存和加载对象

在视类的OnLButtonUp函数中,当绘制图形之后,就要将图形要素保存起来。这里用CObArray来实现,在添加元素的时候添加的是CObject指针,因为CGraph类就是从CObject类派生而来的。

为了保存多个图形要素,为视类添加一个CObArray类型的成员变量:m_obArray,将其访问权限设置为public。
将OnLButtonUp函数修改为:

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
 // TODO: Add your message handler code here and/or call default
 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(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);
 CScrollView::OnLButtonUp(nFlags, point);
}

对于一个文档类对象来说,可以有多个视类对象与之相关,对于一个视类对象来说,它只能与一个文档类对象相关。
为了获得文档对象相关的视类对象,首先通过CDocument类的GetFirstViewPosition成员函数获得与该文档对象相关的视类链表中第一个视类对象的位置;然后通过GetNextView函数得到当前位置所指示的视类对象指针:

void CGraphicDoc::Serialize(CArchive& ar)
{
 POSITION pos=GetFirstViewPosition();
 CGraphicView *pView=(CGraphicView*)GetNextView(pos);
 if (ar.IsStoring())
 {
  // TODO: add storing code here
  int nCount=pView->m_obArray.GetSize();
  ar<<nCount;
  for(int i=0;i<nCount;i++)
  {
   ar<<pView->m_obArray.GetAt(i);
  }
 }
 else
 {
  // TODO: add loading code here
  int nCount;
  ar>>nCount;
  CGraph *pGraph;
  for(int i=0;i<nCount;i++)
  {
   ar>>pGraph;
   pView->m_obArray.Add(pGraph);
  }
 }
}

并在源文件中添加一下头文件:
#include “GraphicView.h”
#include “Graph.h”

在提取文件数据时,当我们将CGraph对象添加到集合对象之后,还需要将这些图形在程序窗口中显示出来,这可以在CGraphicView类的OnDraw函数中把图形的重绘工作完成:

void CGraphicView::OnDraw(CDC* pDC)
{
 CGraphicDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 // TODO: add draw code for native data here
 int nCount;
 nCount=m_obArray.GetSize();
 for(int i=0;i<nCount;i++)
 {
  ((CGraph*)m_obArray.GetAt(i))->Draw(pDC);
 }
}

运行程序,进行绘图,然后保存。关闭程序,再次运行,打开就可以看到先前绘制的图形了。

13.4.3版本号

将之前IMPLEMENT_SERIAL(CGraph,CObject,1)中的1改为2.
运行发现报错,原因是先保存的版本是1,现在读取时发现版本为2了,不一致就会出现报错现象。

13.5文档对象数据的销毁

当新建一个文档对象时候,先前文档所保存的数据没有被销毁。这里主要指CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);

然而在堆上分配内存必须由程序员自己去释放
在OnNewDocument函数中调用了DeleteContents函数。该函数是一个虚函数,

void CGraphicDoc::DeleteContents() 
{
 // TODO: Add your specialized code here and/or call the base class
 int nCount;
 nCount=m_obArray.GetSize();
 for(int i=0;i<m_obArray.GetSize();i++)
 {
  delete m_obArray.GetAt(i);
  m_obArray.RemoveAt(i);
 }
 CDocument::DeleteContents();
}

用delete函数删除指针指向的堆内存,但是对于m_obArray数组所保存的元素来说,其内存并没有删除,它所保存的指针值还是存在的。CObArray类中有一个成员函数:RemoveAt,可以删除指定索引处的元素。

对于刚才的这种写法,因为删除元素之后,它的大小就会发生变化,所以第三次循环时,索引i是2,而GetSize函数返回值是1.
为了删除m_obArray数组中的所有元素,并不需要再一次循环,注意删除,因为CObArray类中还有一个成员函数:RemoveAll,用于从这个数组中删除所有元素。

for(int i=0;i<m_obArray.GetSize();i++)
{
 delete m_obArray.GetAt(i);
}
m_obArray.RemoveAll();

另外还可以采用另一种实现方式,可以从索引最大的元素开始删除:

while(nCount--)
{
 delete m_obArray.GetAt(nCount);
 m_obArray.RemoveAt(nCount);
}

你可能感兴趣的:(VC++孙,c++,mfc,c语言,windows,vc++)