通过存档存储及加载 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();
}