MFC默认文件菜单及串行化

MFC的默认文件菜单,提供了新建打开保存”“另存为等功能。序列化(串行化)是微软提供的用于对对象进行文件I/O的一种机制,该机制在框架(Frame)/文档(Document)/视图(View) 模式中得到了很好的应用。打开保存另存为都涉及到对象的串行化。

一、       串行化条件

一个类若需要支持串行化,则需要:

Ø  这个类从CObject派生。串行化要求对象从CObject派生,或者从一个CObject的派生类派生。因为这是MFC中实现RTTI、动态创建的基础。

Ø  该类实现了Serialize函数。Serialize函数是对象真正保存数据的函数,是整个串行化的核心。实现方法和CWuyaDoc::Serialize()一样,利用CArchive::IsStoring()CArchive::IsLoading() 判断当前的操作,并选择<<>>来保存和读取对象。

Ø  该类在定义时使用了DECLARE_SERIAL宏。

Ø  在类的实现文件中使用了IMPLEMENT_SERIAL

Ø  这个类有一个不带参数的构造函数,或者某一个带参数的构造函数所有的参数都提供了缺省参数。

可串行化对象条件中没有包括简单类型,对于简单类型,CArchive基本都实现了运算符<<>>的重载,所以可以直接使用串行化方式进行读写。

简单的可串行化的类如下:

头文件(Wuya.h

#pragma once class CWuya : public CObject { DECLARE_SERIAL(CWuya) public: CWuya(); virtual ~CWuya(); virtual void Serialize(CArchive& ar); private: CString m_strText; int m_nXML; };

实现文件(wuya.cpp

#include "stdafx.h" #include "Wuya.h" IMPLEMENT_SERIAL(CWuya,CObject,1 | VERSIONABLE_SCHEMA) CWuya::CWuya() : m_nXML(0) { } CWuya::~CWuya() { } void CWuya::Serialize(CArchive& ar) { // 调用基类的串行化函数 CObject::Serialize(ar); if(ar.IsStoring()) // 判断是否在进行存储操作 { ar << m_strText << m_nXML; } else { ar >> m_strText >> m_nXML; } }

二、       串行化的版本控制

IMPLEMENT_SERIAL宏的第三个参数可以传入类的当前版本。倘若类wuya增加了一个成员变量CString m_strLine,同时修改Serialize函数:

void CWuya::Serialize(CArchive& ar) { // 调用基类的串行化函数 CObject::Serialize(ar); if(ar.IsStoring()) { ar << m_strText << m_nXML << m_strLine; } else { ar >> m_strText >> m_nXML >> m_strLine; } }

这时,将IMPLEMENT_SERIAL的第三个参数由1改为2。此时这个版本参数是没有任何作用的,CArchive按照一个对象一个对象读取,读到文件末尾,然后需要读入m_strLine对象,导致抛出CArchiveException异常,提示意外的文件格式

1、  为了了解MFC对于串行化的整个流程,可以查看MFC的源代码,当点击菜单打开时:

1>     CDemoApp 中的消息映射ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen),导致调用CWinApp::OnFileOpen函数;

2>     调用m_pDocManager->OnFileOpen(),导致调用CDocManagerOnFileOpen()

3>     调用函数DoPromptFileName(),弹出选择文件对话框;

4>     同时调用AfxGetApp()->OpenDocumentFile(newName)

5>     调用CWinApp中的m_pDocManager->OpenDocumentFile(lpszFileName);

6>     调用CDocument* CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)

7>     函数之后调用pBestTemplate->OpenDocumentFile(szPath),其中pBestTemplateCDocTemplate的指针,将根据单、多文档,分别调用CSingleDocTemplateCMultiDocTemplate中的OpenDocumentFile()函数,将创建视图、文档、子框架对象。而后调用pDocument->OnOpenDocument(lpszPathName)

8>     BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)中调用Serialize()函数,并进行了异常处理。由于CDocumentCOjbect派生,CDemoDocCDocument,而CDemoDoc覆写Serialize ()函数,调用CDocument中的Serialize函数。

9>     CDocument中的Serialize()函数调用发生异常,当前函数未进行处理,将栈展开到函数CDocument::OnOpenDocument中,从而提示意外的文件格式

10>  文件打开成功后,将继续执行CSingleDocTemplate::OpenDocumentFile()中函数InitialUpdateFrame(pFrame, pDocument, bMakeVisible),从而调用函数框架pFrame->InitialUpdateFrame(pDoc, bMakeVisible),而该函数中,对视图发送了WM_INITIALUPDATE消息,对视图进行初始化。

2、  若要是存储的内容可以读出,并没有意外的文件格式的提示,需要修改类的Serialize函数。修改如下

void CWuya::Serialize(CArchive& ar) { // 调用基类的串行化函数 CObject::Serialize(ar); if(ar.IsStoring()) { ar << m_strText << m_nXML << m_strLine; } else { UINT uSchema = ar.GetObjectSchema(); ///< 读取存储的类版本 switch(uSchema) { case 1: ar >> m_strText >> m_nXML; m_strLine.Empty(); break; case 2: ar >> m_strText >> m_nXML >> m_strLine; break; default: AfxThrowArchiveException(CArchiveException::badSchema); break; } } }

                 GetObjectSchema()函数将读取文件中该类的版本号,可以根据该版本号进行判断读取的时候应该采取什么方法。以上的Serialize函数可以保证当前版本2打开版本1存储的文件。

3、  解释

1>     IMPLEMENT_SERIAL宏展开之后,可以看到只针对操作符 >> 进行了重载。这是由于ReadObject()函数需要CRunTimeClass信息,而WriteObject()函数不需要。这两个函数分别在 >> << 时调用。

2>     由于MFC的串行化涉及到动态创建,而动态创建的过程是使用new关键字创建的对象,在CDemoDocSerialize()函数中,串行化的对象都应该是指针对象,或是存放到CArrayCTypedPtrArray的指针数组。对于指针对象,应该在OnNewDocument()函数中进行初始化,并在DeleteContents()函数中进行内存释放,该函数在新建、打开、关闭时都将被调用。

3>     只通过Serialize函数对对象读写,而不使用ReadObject/WriteObject和运算符重载时,前面的可串行化条件不需要,只要实现Serialize 函数即可。或是对于现存的类,如果它没有提供串行化功能,可以通过使用重载友元operator <<operator >>来实现。这两种方法也可以实现串行化,但如果这么做,将无法使用GetObjectSchema()获取版本号的功能。实现版本兼容将比较困难。

三、       MFC中默认文件菜单

看完了Serialize()函数中的文件读取,接着看看其他文件菜单的内部操作。在doccore.cpp中,可以看到CDocument类的消息映射中,存在如下代码:

         BEGIN_MESSAGE_MAP(CDocument, CCmdTarget) //{{AFX_MSG_MAP(CDocument) ON_COMMAND(ID_FILE_CLOSE, &CDocument::OnFileClose) ON_COMMAND(ID_FILE_SAVE, &CDocument::OnFileSave) ON_COMMAND(ID_FILE_SAVE_AS, &CDocument::OnFileSaveAs) //}}AFX_MSG_MAP END_MESSAGE_MAP()

分别相应了关闭保存另存为菜单的操作。

1、 关闭菜单

1>     void CDocument::OnFileClose()函数调用OnCloseDocument()函数,此函数为虚函数,可以再CDemoDoc中进行覆写。默认的操作关闭所有的视图,并调用虚函数DeleteContents()做清除工作。DeleteContents()函数在打开文档、新建文档时都将先被调用。

2>     DeleteContents()函数为虚函数,可以再CDemoDoc中进行覆写。

2、 保存菜单

1>     void CDocument::OnFileSave()调用DoFileSave()函数。

2>     DoFileSave()函数调用DoSave()函数。

3>     DoSave()函数调用OnSaveDocument()函数,该函数为虚函数。

4>     默认的OnSaveDocument()函数中调用Serialize()函数进行串行化保存。

另存为操作与保存类似,直接调用了DoSave()函数进行保存。

3、 新建菜单

1>     CDemoApp的消息映射中:

ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)

将新建菜单映射为函数CWinAppOnFileNew函数。

2>     经过类似打开操作的步骤,到调用BOOL CDocument::OnNewDocument()函数。

3>     此函数也为虚函数,可以对该函数进行覆写。

你可能感兴趣的:(其他)