Windows程序设计__孙鑫C++Lesson13《文档与串行化》
本节要点:
1.认识CArchive类及串行化操作
2.应用程序相关字段的修改和获取
3.断点跟踪法,了解单文档的OnFileNew和OnFileOpen执行过程
4.MFC文档管理(初步浅析,这部分内容是比较复杂的)
5.程序设计技巧--资源拷贝的方法
6.文档串行化的实现
7.文档内存空间的释放
//**************************************************************************
1.认识CArchive类及串行化操作
CArchive必须与一个文件关联,通过打开一个文件,然后构造一个CArchive对象,利用这个对象来进行文件操作。
在文档类中初步试验CArchive类的串行化,实验代码如下:
//**************************************************************************
void CGraphicDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
int i=4;
char ch='a';
float f=1.4f;
CString str="CArchive Experiment";
ar<<i<<ch<<f<<str;
}
else
{
// TODO: add loading code here
int i;
char ch;
float f;
CString str;
ar>>i>>ch>>f>>str;
CString msg;
msg.Format("%d,%c,%f,%s",i,ch,f,str);
AfxMessageBox(msg);
}
}
//***************************************************************************
2.应用程序相关字段的修改和获取
CDocument文档类 修改窗口标题两种方法:
方法一:CGraphicDoc::OnNewDocument()函数用SetTitle修改标题
方法二:利用资源字符串修改标题(也可以修改其他比如文件类型后缀等字段) IDR_MAINFRAME字符串的加载单文档模板时加载了资源包括了字符串,
修改该字符串中对应字段的值可以修改创口标题。
另外CDocTemplate 文档模板类中的方法GetDocString 可以查找IDR_MAINFRAME字符串中的各个子串。
通过该函数可查询的七个值,正好对应字符串资源中的内容,并且顺序也保持一致。如CDocTemplate::windowTitle表示了应用程序窗口标题.
3.断点跟踪法,了解单文档的OnFileNew和OnFileOpen执行过程
//***********************************************************************
//单文档的OnFileNew执行过程
CWinApp::OnFileNew()
CDocManager::OnFileNew()
{
pTemplate->OpenDocumentFile(NULL)//单文档模板指针
}
CSingleDocTemplate->OpenocumentFile()
{
pDocument->CreateNewDocument()
pDocument->CreateNewFrame()
pDocument->OnNewDocument()//利用自子类指针调用OnNewDocument 调用CGraphicDoc::OnNewDocument()
}
CGraphicDoc::OnFileNew()
//***********************************************************************
单文档的OnFileOpen执行过程
CWinApp::OnFileOPen()
CDocManager::OnFileOPen()
{
DoPromptFileName()//弹出文件打开对话框 选择文件
AfxGetApp()->OpenocumentFile(newName)
}
CWinApp::OpenocumentFile(newName);
CDocManager::OpenocumentFile();
{
CDocument *pOpenDocument=NULL;
CDocTemplate* pBesttemplate=NULL;
....
CDocTemplate *pTemplate=(CDocTemplate *)m-templateList.GetNext(pos);
pTemplate->MatchDocType(szPath,pOpenDocument);
//关键之处 保存文档后打开文档时若文档已经打开
//则pOpenDocument不再为空,执行return pOpenDocument,直接返回,不再执行子类的Serialize。
//pOpenDocument为空则执行return pBestTemplate->OpenDocumentFile(szPath);
}
CSingleDocTemplate::OpenDocumentFile()
CDocument::OnOpenDocument() 构造CFile和CArchive 调用虚函数Serialize调用子类的Serialize
函数。
void CGraphicDoc::Serialize(CArchive& ar);
//***********************************************************************
4.MFC文档管理(初步浅析,这部分内容是比较复杂的)
MFC的框架、视图、文档实际上是将文件操作分为不同模块,
视图类负责数据显示和用户操作,文档类负责数据保存和加载。
新建和打开菜单项在CWinApp响应,CWinApp有一个成员变量CDocManager* m_pDocManager指向CDocmanager,
文档管理器中指针链表保存文档模板,文档模板负责管理文档类、框架类、视类。
更具体的内容参见下图:
5.程序设计技巧--资源拷贝的方法
从另外一个工程拷贝一个菜单资源到当前工程,需要用当前工程打开先前工程,将其菜单资源复制过来, 注意观察ID,若需要则修改其ID。
6.文档串行化的实现
(1)认识Serialization
这个知识点将在后期专门作一篇博文,以期完全掌握串行化,在这里不做赘述。
(2)文档类与视类的通信:
一个文档类对象可以和多个视类对象相关,而一个视类对象只能和一个文档类对象相关。
单文档的文档类通过GetFirstViewPosition 和GetNextView 迭代找到视类指针;而视类通过已经由MFC生成的函数GetDocument()来获取文档类指针.
至于多文档的则稍复杂,这里暂时没有介绍。可以参见我后期的博文或上网查阅。
(3)实现一个支持串行化的类:
步骤:参见MSDN Serialization: Making a Serializable Class主题文章.
Five main steps are required to make a class serializable.
step1.eriving your class from CObject (or from some class derived from CObject).
step2.Overriding the Serialize member function.
step3.Using the DECLARE_SERIAL macro in the class declaration.
step4.Defining a constructor that takes no arguments.
step5.Using the IMPLEMENT_SERIAL macro in the implementation file for your class.
CGraph类的串行化代码如下:
//***************************************************************************
//Graph.h
class CGraph :public CObject //step1 从CObject派生一个类
{
DECLARE_SERIAL(CGraph) //step3 声明串行化
public:
CGraph();
CGraph(int nDrawType,CPoint ptOrigin,CPoint ptEnd);
virtual ~CGraph();
public:
void Draw(CDC* pDC);
void Serialize( CArchive& archive );//step2 重写Serialize函数
int m_nDrawType;
CPoint m_ptOrigin;
CPoint m_ptEnd;
};
//Graph.cpp
IMPLEMENT_SERIAL( CGraph, CObject, 1 )//step5 在实现文件中使用IMPLEMENT_SERIAL宏
CGraph::CGraph(){ } //step4 定义无参数的构造函数
CGraph::CGraph(int nDrawType,CPoint ptOrigin,CPoint ptEnd)
{
m_nDrawType=nDrawType;
m_ptOrigin=ptOrigin;
m_ptEnd=ptEnd;
}
CGraph::~CGraph(){}
void CGraph::Serialize(CArchive &archive)
{
CObject::Serialize( archive );
// now do the stuff for our specific class
if( archive.IsStoring() )
archive << m_nDrawType << m_ptOrigin<<m_ptEnd;
else
archive >> m_nDrawType >> m_ptOrigin>>m_ptEnd;
}
void CGraph::Draw(CDC *pDC)
{
CBrush* pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
CBrush *pOldBrush=pDC->SelectObject(pBrush);
switch(m_nDrawType)
{
case 0:
pDC->SetPixel(m_ptEnd.x,m_ptEnd.y,RGB(255,0,0));
break;
case 1:
pDC->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
pDC->LineTo(m_ptEnd.x,m_ptEnd.y);
break;
case 2:
pDC->Rectangle(CRect(m_ptOrigin,m_ptEnd));
break;
case 3:
pDC->Ellipse(CRect(m_ptOrigin,m_ptEnd));
break;
default:
break;
}
pDC->SelectObject(pOldBrush);
}
//***************************************************************************
(4)图形的保存 利用数据结构CObArray来操作
这里注意,不管是CDoc类中对Graph串行化,还是利用CObArray来串行化,底层的数据保存工作都是由CGraph来完成的,
并且串行化是总是调用子类的Serialize函数实现,子类应该对串行化的具体实现负责。
串行化实验代码如下:
//***************************************************************************
void CGraphicDoc::Serialize(CArchive& ar)
{
/*串行化方法一 复杂的
POSITION pos=GetFirstViewPosition();
CGraphicView* pView=(CGraphicView *)GetNextView(pos);//获取视类对象指针
if(ar.IsStoring())
{
int cnt=pView->m_obrGraph.GetSize();
int index=0;
ar<<cnt;
for(index=0;index<cnt;index++)
ar<<pView->m_obrGraph.GetAt(index);//调用了对象本身的Serialize函数实现
}
else
{
int cnt;
CGraph *pGrapg;
int index=0;
ar>>cnt;
for(index=0;index<cnt;index++)
{
ar>>pGrapg;//无需构造对象 自动构造返回首地址
pView->m_obrGraph.Add(pGrapg);
}
}
*/
/* 串行化方法二 简便的 利用CObArray结构的串行化操作
CObArray m_obrGraph定义在View类时
POSITION pos=GetFirstViewPosition();
CGraphicView* pView=(CGraphicView *)GetNextView(pos);//获取视类对象指针
pView->m_obrGraph.Serialize(ar);
*/
m_obrGraph.Serialize(ar);//CObArray m_obrGraph定义在Doc类时
}
//***************************************************************************
实际上上述,方法一的代码和CObArray内部的代码是差不多的。CObArray串行化操作过程如下:
void CObArray::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
CObject::Serialize(ar);
if (ar.IsStoring())
{
ar.WriteCount(m_nSize);
for (int i = 0; i < m_nSize; i++)
ar << m_pData[i];
}
else
{
DWORD nOldSize = ar.ReadCount();
SetSize(nOldSize);
for (int i = 0; i < m_nSize; i++)
ar >> m_pData[i];//CObject** m_pData;
}
}
//***************************************************************************
7.文档内存空间的释放
(1)内存空间释放位置
CDocument::OnNewDocument CDocument::OnCloseDocument CDocument::OnOpenDocument
三个函数中都要调用同一个函数,这个函数就是CDocument::DeleteContents ,
这个函数清除文档类的数据,因此释放内存空间的操作应该放在这个函数实现,
同时这个函数是个虚函数,用户可以重载该函数实现内存空间的清除操作。
(2)CObArray数据的清除
注意两点:第一CObArray的RemoveAt和RemoveAll函数只是删除了数组中的指针,
而没有释放由指针指向的对象的空间,这个操作由用户自己完成;
第二,CObArray的RemoveAt函数删除指针的机制比较特别,他将删除由Index所指的元素,
同时将所有在删除元素之上(索引大一些的元素)的元素下移,没有注意到这一点将发生错误。
关于CObArray::RemoveAt函数出错的实验过程如下:
//*************************************************************************
void CGraphicDoc::DeleteContents()
{
// TODO: Add your specialized code here and/or call the base class
int nCnt=m_obrGraph.GetSize();
int index=0;
for(index=0;index<nCnt;index++)
{
delete m_obrGraph.GetAt(index);
m_obrGraph.RemoveAt(index);//错误
}
}
//*************************************************************************
关于CObArray::RemoveAt 执行错误的理解
当再执行循环时,无法删除index=2的元素(出错),同时index=0的元素没有删除。
删除过程如下:
{"delete index=0,info:type=1,ptOrigin=301,ptEnd=65"}删除Obj1
{"delete index=1,info:type=1,ptOrigin=210,ptEnd=122"}删除Obj3
而{"index=0,info:type=1,ptOrigin=539,ptEnd=69"}Obj2没有被删除
RemoveAt 错误调试程序
void CGraphicDoc::DeleteContents()
{
// TODO: Add your specialized code here and/or call the base class
int nCnt=m_obrGraph.GetSize();
int index=0;
CGraph *pGraph;
CString msg;
//查看m_obrGraph中内容
for(index=0;index<nCnt;index++)
{
pGraph=(CGraph*)m_obrGraph.GetAt(index);
msg.Format("index=%d,info:type=%d,ptOrigin=%d,ptEnd=%d",index,
pGraph->m_nDrawType,pGraph->m_ptOrigin,pGraph->m_ptEnd);
AfxMessageBox(msg);
}
//观察删除过程
for(index=0;index<nCnt;index++)
{
if(index==2)//第三次执行时,实际上m_obrGraph中0号索引的内容并没有释放掉
{
pGraph=(CGraph*)m_obrGraph.GetAt(0);
msg.Format("index=%d,info:type=%d,ptOrigin=%d,ptEnd=%d Still Remain!",
0,pGraph->m_nDrawType,pGraph->m_ptOrigin,pGraph->m_ptEnd);
AfxMessageBox(msg);
}
pGraph=(CGraph*)m_obrGraph.GetAt(index);
msg.Format("delete index=%d,info:type=%d,ptOrigin=%d,ptEnd=%d",
index,pGraph->m_nDrawType,pGraph->m_ptOrigin,pGraph->m_ptEnd);
AfxMessageBox(msg);
delete pGraph;
m_obrGraph.RemoveAt(index);//错误
}
CDocument::DeleteContents();
}
//********************************************************************************
正确的空间释放操作方法有两种,代码如下:
//***************************************************************************
void CGraphicDoc::DeleteContents()
{
// TODO: Add your specialized code here and/or call the base class
int nCnt=m_obrGraph.GetSize();
int index=0;
//方法一 先删除指针指向控件 然后依次删除指针
for(index=0;index<nCnt;index++)
{
delete m_obrGraph.GetAt(index);
}
m_obrGraph.RemoveAll();
/*
//方法二 从高序号开始删除
for(index=nCnt-1;index>=0;index--)
{
delete m_obrGraph.GetAt(index);
m_obrGraph.RemoveAt(index);
}
*/
CDocument::DeleteContents();
}
//***************************************************************************
本节小结:
1.掌握如何使一个类支持串行化操作,并且利用CObArray来快速实现类的串行化.
2.认识MFC单文档的新建、打开、保存操作的执行过程,掌握断点跟踪法这种思想.
3.初步了解MFC文档的管理机制.