Windows程序设计__孙鑫C++Lesson13《文档与串行化》

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文档的管理机制.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值