/********************************************/

第十六章:读和写文档--SDI应用程序


一,序列化:

1,序列化:对象可以被持续,即当程序退出时候它们被存盘,当程序重启时候它们又可被恢复,对象这种存盘和恢复处理过程称之为序列化。
注意:MFC库中,不能利用序列化来代替数据库管理系统。与文档相关的所有对象只能在某个单独的磁盘文件中进行顺序读写,而不支持对象在磁盘文件中随机读写。

2,磁盘文件和归档(Archives):
1)在MFC库中,磁盘文件是通过CFile类的对象来表示的。
2)如果应用程序不直接利用磁盘I/O,而只依赖于序列化处理过程,则可以避免直接使用CFile对象。
3)在serialize函数(序列化函数)与CFile对象之间,还有一个归档对象(CArchive类对象),归档对象为CFile对象缓存数据,同时还保存一个内部标记,用来标示归档存档(写盘)还是载入(读盘)。
4)每次只能有一个活动的归档与文件相连。应用程序框架会很好管理CFile对象及CArchive对象的创建,为CFile对象打开相应的磁盘文件,并且将相应的归档对象与文件对象相连。
5)关系图:
持续文档对象 <--> Serialize <--> CArchive对象 <--> CFile对象 <--> 磁盘
6)当用户选择了File Open或File Save命令时,应用程序框架自动调用Serialize函数。

3,使类可序列化:
1)可序列化的类必须直接或间接从CObject派生,并且在类声明中,必须包括DECLARE_SERIAL宏调用,在类的实现文件中必须包括IMPLEMENT_SERIAL宏调用。
2)编写Serialize函数。
如:
// example for CObject::Serialize
void CAge::Serialize( CArchive& ar )
 {
 CObject::Serialize( ar );
     if( ar.IsStoring() )//IsStoring来判断当前归档是被用来存入还是被用来载入的。
     ar << m_years;
     else
     ar >> m_years;
 }
注意一:插入运算符对值重载,析取运算符对应用重载,有时必须强制转换才能适应编译器:
如:m_nType是枚举类型,则:ar>>(int&)m_nType;ar<<(int)m_nType;
注意二:插入和析取运算符并不适用于CObject派生类内嵌对象。
3)大多数序列化函数都需要调用基类的Serialize函数(一般在第一行调用,如CStudent是从CPerson派生,那么CStudent中Serialize函数第一行就应该为CPerson::Serialize(ar);)。CObject类Serialize是虚函数并且没做任何工作,因而2)中类子中没调用CObject::Serialize函数。
4)对于CObject派生类的内嵌对象中的Serialize函数中总是需要直接调用内嵌对象的Serialize函数。

4,使集合序列化:
1)所有的集合类都是从CObject类派生,并且在集合类声明中都包含有DECLARE_SERIAL宏调用,应此可以调用集合类的Serialize成员函数,方便使集合序列化。
2)如调用了由一组对象组成的COblist集合的Serialize函数,则COblist集合中每个对象的Serialize函数会被依次调用。
3)如果集合中包含了指向不同类对象的指针,则所有的类名都被相应地存储到归档中,以便所有的对象在被创建时都能够调用相应类的构造函数。
4)如果包容对象(如文档)中包含了一个内嵌集合,则被装入的数据会被追加进现存集合中。(因此有必要在载入前对现存的集合清空。通常由DeleteContents函数完成。)
5)当CObject指针集合被从归档中载入时,集合中的每个对象会被按如下步骤处理:
×指定对象的类。
×为每一个对象分配堆存储空间。
×将对象数据载入到新申请的内存中。
×将新对象的指针存进集合中。
6)当用户选择了File菜单的Save或Open菜单项,应用程序框架随即会创建CArchive对象(以及内部的CFile对象),然后再调用文档类的Serialize函数,并且将CArchive对象的引用传递给它。然后派生文档类的Serialize函数会对每一个非临时数据成员进行序列化。


二,SDI应用程序:

1,SDI MFC应用程序的具体启动步骤:
1)Windows将程序装入内存。
2)构造全局对象theApp。(当程序被载入时候,所有的全局对象都会立刻被创建。)
3)Windows调用全局函数WinMain。
4)WinMain自动搜索CWinApp派生类的唯一实例。
5)WinMain调用theApp的InitInstance函数,该函数在派生应用程序中被重载。
6)被重载的InitInstance函数启动文档的载入以及主框架和视图窗口的显示处理过程。
7)WinMain调用theApp的Run成员函数,启动窗口消息和命令消息的分发处理过程。
还可以对其它的CWinApp成员函数进行重载。当应用程序被中止,且所有窗口被关闭后,ExitInstance函数被调用。

2,派生的应用程序类InitInstance函数中可以看到这样代码:
CSingleDocTemplate* pDocTemplate;
 pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CMy15aDoc),
  RUNTIME_CLASS(CMainFrame),       // main SDI frame window
  RUNTIME_CLASS(CMy15aView));
 AddDocTemplate(pDocTemplate);
通过这组代码将程序类,文档类,视图窗口类以及主框架类建立类之间的相互关系(注意这里是类之间相互关系)。
(即通过AddDocTemplate调用将所有的应用程序元素联系在一起)
说明:
1)应用程序对象在模板被创建之前已经存在,但此时文档,视图,以及框架对象还没有被创建。应用程序框架在以后需要的时候会通过动态创建这些对象,这里是通过适用RUNTIME_CLASS来实现的。
2)P338页两个图:类之间关系图 和 对象之间关系图 这里掠过,找电子版看看。
3)IDR_MAINFRAME 是用来标示字符串表资源的。IDR_MAINFRAME 所标示的字符串被分成一些以"/n"结尾的子字符串。当应用程序执行时候,这些子字符串会在各种地方出现。
在RC文件StringTable中可以找到IDR_MAINFRAME串,串的具体对应关系参看P339页。

3,SDI文档多视图两种技术:提供菜单项供用户选择视图,将多视图安排在切分窗口中。

4,创建空文档……CWinApp::OnFileNew函数
应用程序类的在调用了AddDocTemplate函数之后,通过CWinApp::ProcessShellCommand函数间接调用了CWinApp成员函数OnFileNew完成以下工作:
1)构造文档对象,但并不从磁盘中读取数据。
2)构造主框架对象,并创建主框架窗口,但并不对其显示。主框架窗口包括IDR_MAINFRAME菜单,工具栏和状态栏。
3)构造视图对象,并创建视图窗口,但并不对它进行显示。
4)建立文档,主框架和视图对象之间的相互联系(注意这里是对象之间联系)
5)调用文档对象的OnNewDocument虚成员函数。它会调用DeleteContents虚函数。
6)调用视图对象的CView::OnInitiaUpdate虚成员函数。
7)调用框架对象的CFrameWnd::ActivateFrame虚成员函数,以便显示出具有菜单,视图窗口和控制栏的主框架窗口。
说明:上面一些函数是通过框架间接调用。

在SDI应用程序中,文档,主框架以及视图对象都仅被创建一次,并且将存在于程序的整个运行过程中。
CWinApp::OnFileNew函数被InitInstance函数所调用,当用户选择了File New 菜单项的时候也调用(此时不需要再构造文档框架视图对象,而是利用现存的完成上述后三个步骤)。
注意:OnFileNew函数总是要调用DeleteContents函数,以便将文档清空。

5,CDocument::OnNewDocument
如果SDI应用程序不重新使用相同的文档对象,则也就没必要使用OnNewDocument函数(因为可以在文档类构造函数中完成所有的文档初始化工作)。但一般还是得(必须)对OnNewDocument函数进行重载,以便每次用户选择File New或File Open时候,都能利用它来初始化文档对象。
说明:
一般,在构造函数里尽量少的做一些工作,构造函数做的工作越少,失败的机会就越小。象CDocument::OnNewDocument和CView::OnInitialUpdate这样的函数是完成初始化工作的好地方。

6,连接File Open与系列化代码………OnFileOpen函数
当AppWizard创建应用程序时,它会自动将File Open菜单项映射到CWinApp的OnFileOpen成员函数。
OnFileOpen函数进一步调用一组函数来完成一下工作:
1)提示用户选择一个文件。
2)调用已经存在文档对象的CDocument::OnOpenDocument虚成员函数。该函数将打开文件,调用DeleteContents函数,再创建一个用于装入数据的CArchive对象,然后调用文档的Serialize函数从归档中载入数据。
3)调用视图的OnInitialUpdate函数。

除了使用File Open菜单项外,还可以选择使用最近使用过的文件列表。(框架会记录4个最近使用过的文件,并将它们的名字显示在File菜单中。这些文件名在程序的运行过程中被记录在Windows的注册表中。
说明:
可以改变最近使用文件数。(在InitInstance函数里为LoadStdProfileSetting函数提供适当参数)
CWinApp::LoadStdProfileSettings
void LoadStdProfileSettings( UINT nMaxMRU = _AFX_MRU_COUNT );
//nMaxMRU:The number of recently used files to track.
//Call this member function from within the InitInstance member function to enable and load the list of most recently used (MRU) files and last preview state. If nMaxMRU is 0, no MRU list will be maintained.

7,CDocument::DeleteContents
1)当File New或File Open菜单项被选中CDocument::OnNewDocument或CDocument::OnOpenDocument函数都要调用CDocument::DeleteContents函数(清除现存文档对象内容)。
即:当文档对象第一次被创建之后,CDocument::DeleteContents 会被立刻调用,而当文档被关闭,它再次被调用。
2)常在派生类中重载DeleteContents 函数完成对文档对象中内容清除工作。
3) CDocument::DeleteContents      Called by the framework to delete the document’s data without destroying the CDocument object itself. It is called just before the document is to be destroyed. It is also called to ensure that a document is empty before it is reused. This is particularly important for an SDI application, which uses only one document; the document is reused whenever the user creates or opens another document. Call this function to implement an “Edit Clear All” or similar command that deletes all of the document’s data. The default implementation of this function does nothing. Override this function to delete the data in your document.

8,将File Save 和File Save As与系列化代码相连接:
1)当AppWizard在创建应用程序时,将File Save菜单项映射到CDocument类的OnFileSave成员函数。
2)OnFileSave函数调用CDocument::OnSaveDocument函数,OnSaveDocument函数使用归档对象(存入)来调用文档的Sarialize函数。
3)File Save As菜单象以类似方式处理。映射CDocument::OnFileSaveAs函数,OnFileSaveAs函数会调用OnSaveDocument函数。
4)文档存盘的必要的文件管理工作都是由应用程序框架来完成。
注意:
File New和File Open被映射到应用程序类成员函数,而File Save和File Save As则被映射到文档类成员函数。

9,文档的‘胀’标志
许多面向文档的应用程序跟踪用户对文档的修改,当用户试图关闭文档或退出时候,应用程序弹出对话框询问是否需要将文档保存。
1)MFC通过CDocument::m_bModified来支持这种功能。文档被修改(变胀了)则m_bModified=TRUE,否则为FALSE。
2)m_bModified为protected类型,可以通过CDocument类中的SetModifiedFlag和IsModified成员函数来访问。(注意:在MSDN中不能查出CDocument类m_bModified数据成员,不过追踪CDocument类定义可以发现如下定义:protected: BOOL m_bModified; )
3)CDocument::SetModifiedFlag
void SetModifiedFlag( BOOL bModified = TRUE );

CDocument::IsModified
BOOL IsModified( );

10,程序注册
1)利用AppWizard在程序创建Step-4使用Advanced选项为程序添加文件扩展名(或在StringTable里IDR_MAINFRAME里添加)
2)在InitInstance函数里加入下列代码行:RegisterShellFileTypes(TRUE);(注意添加的位置在AddDocTemplate函数之后!)

11,允许程序的拖放:
如果希望运行中的程序可以打开拖到其上的文件,则必须在主程序框架窗口中调用CWnd的DragAcceptFiles函数。
在InitInstance函数中(函数未)添加下列代码:m_pMainWnd->DragAcceptFiles();

//
/
///

Logo

快速构建 Web 应用程序

更多推荐