📄 chap7_2.htm
字号:
:: 把m_nShellCommand成员置为CCommandLineInfo::FileNew,这导致ProcessShellCommand成员函数调用OnFileNew。用户可在InitInstance()中显式的调用OnFileNew()。</font></td>
</tr>
</table>
</center></div>
<p><font ><br>
应用程序对象的OnFileNew消息处理流程如下:首先判断应用程序是否有多个文档模板,若是,则显示一个对话框让用户选择创建哪种类型的文档(模板)。对话框中显示的字符串是与文档模板对象的构造函数的第一个参数相对应的字符串(若资源中无相应字符串则不显示)。然后该函数调用CDocManager::OpenDocumentFile(NULL)成员函数,打开一个新文件。CDocManager::OpenDocumentFile函数调用了CSingleDocTemplate的OpenDocumentFile,后者完成实际的创建文档、框架、视图工作。文档模板的OpenDocumentFile首先判断文档是否已经被创建,若未创建,则创建一个新文档。然后根据文件名参数是否为空,分别调用CDocument的OnNewDocument(
)和CDocument的OnOpenDocument()函数。CDocument的OnNewDocument首先调用DeleteContents(),并将文档修改标志该为FALSE(关闭窗口时将根据文档修改标志决定是否提示用户保存文档)。</font></p>
<p><font color="#3973DE" >清理文档类的数据成员</font></p>
<p><font > 在关闭应用程序删除文档对象时,或用File->Open菜单打开一个文档时,需要清理文档中的数据。同文档的初始化一样,文档的清理也不是在文档的析构函数中完成,而是在文档的CDocument::DeleteContents()成员函数中完成的(想想为什么?)。析构函数只用于清除那些在对象生存期都将存在的数据项。DeleteContents()成员函数的调用有两个作用:</font></p>
<blockquote>
<p><font >1.删除文档的数据;</font></p>
<p><font >2确信一个文档在使用前为空。</font></p>
</blockquote>
<p><font > 前面已经说到,OnNewDocument函数会调用DeleteContents()函数。在用户选择File->Open菜单时,应用程序对象调用应用程序类的OnFileOpen成员函数,CWinApp::OnFileOpen调用内部的文档管理类CDocManager::OnFileOpen()成员函数,提示用户输入文件名。然后调用CWinApp::OpenDocumentFile打开一个文件。OpenDocumentFile在打开文件后首先调用DeleteContents成员函数清理文档中的数据,确保消除以前打开的文档的数据被清理掉。</font></p>
<p><font > 缺省的DeleteContents函数什么也不做。你需要重载DeleteContents函数,并编写自己的文档清理代码。要重载DeleteContents成员函数:</font></p>
<p><font > 从View菜单下选择ClassWizard,启动ClassWizard,选择Message Maps页。在ClassName下拉列表框中选择CEditorDoc,从ObjectIDs列表框选择CEditorDoc,在Message列表框双击DeleteContents。此时DeleteContents出现在Member
functions列表框中,并被选中。点Edit Code按钮,开始编辑DeleteContents函数定义。在DeleteContents函数体中加入代码后,如清单7.5所示:</font></p>
<p><font >清单7.5 CEditorDoc的DeleteContents成员函数</font></p>
<p><font >void CEditorDoc::DeleteContents() </font></p>
<p><font >{</font></p>
<p><font >// TODO: Add your specialized code here
and/or call the base class</font></p>
<p><font >nLineNum=0;</font></p>
<p><font >/*删除集合类的数据:</font></p>
<p><font >用GetHeadPosition和GetNext遍历并用delete删除其中的数据,然后调
用RemoveAll()删除链表所包含的指针</font></p>
<p><font >*/</font></p>
<p><font >POSITION pos;</font></p>
<p><font >pos=lines.GetHeadPosition();</font></p>
<p><font >while(pos!=NULL)</font></p>
<p><font >{</font></p>
<p><font >((CString)lines.GetNext(pos)).Empty();</font></p>
<p><font >//调用CString的Empty()方法清除文本行的数据,对于其它类型的对
//象,应当调用delete 删除该对象</font></p>
<p><font >}</font></p>
<p><font >lines.RemoveAll();</font></p>
<p><font >CDocument::DeleteContents();</font></p>
<p><font >}</font></p>
<p><font >编辑器的DeleteContents()实现与OnNewDocument()基本相同,别的程序则可能会有所不同。</font></p>
<p><font >CDocument::OnOpenDocument成员函数在调用DeleteContents()函数后,将文档修改标记设置为FALSE(未修改),然后调用Serialize进行文档的串行化工作。</font></p>
<p><font color="#3973DE" >读写文档——串行化</font></p>
<p><font > 文档对象的串行化是指对象的持续性,即对象可以将其当前状态,由其成员变量的值表示,写入到永久性存储体(通常是指磁盘)中。下次则可以从永久性存储体中读取对象的状态,从而重建对象。这种对象的保存和恢复的过程称为串行化。对象的可持续性允许你将一个复杂的对象网络保存到永久性存储体中,从而在对象从内存中删去后仍保持它们的状态。以后,可以从永久性存储器中载入对象并在内存中重载。保存和载入可持续化、串行化的数据通过CArchive对象作为中介来完成。</font></p>
<p><font > 文档的串行化在Serialize成员函数中进行。当用户选择File Save、Save As或Open命令时,都会自动执行这一成员函数。AppWizard只给出了一个Serialze()函数的框架,读者要做的时定制这个Serialize函数。Serialize()函数由一个简单的if-else语句组成:</font></p>
<p><font >void CEditorDoc::Serialze(CArchive&
ar)</font></p>
<p><font >{</font></p>
<p><font >if(ar.IsStoring())</font></p>
<p><font >{</font></p>
<p><font >//TODO: add storing code here.</font></p>
<p><font >}</font></p>
<p><font >else</font></p>
<p><font >{</font></p>
<p><font >//TODO: add loading code here.</font></p>
<p><font >}</font></p>
<p><font >}</font></p>
<p><font > 在框架中,Serialize函数的参数ar是一个CArchive类型对象,它包含一个CFile类型的文件指针(类似于C语言的文件指针),执行一个文件。CArchive对象为读写CFile(文件类)对象中的可串行化数据提供了一种类型安全的缓冲机制。通常CFile代表一个磁盘文件;但它也可以是一个内存文件(CMemFile对象)或剪贴板。一个给定的CArchive对象只能读数据或写数据,而不能同时读写数据。当保存数据到archive对象中时,archive把它放在一个缓冲区中。直到缓冲区满,才把数据写入它所包含的文件指针指向的CFile对象中。同样的,当从archive对象读数据时,archive对象从文件中读取内容到缓冲区,然后再从缓冲区读入到可串行化的对象中。这种缓冲机制减少了访问物理磁盘的次数,从而提高了应用程序的性能。</font></p>
<p><font > 在创建和使用一个CArchive对象之前,必须先创建一个CFile文件类对象。而且还必须确保archive的载入和保存状态同文件打开模式相兼容。幸运的是,应用程序框架已经为我们做好了这些工作。</font></p>
<p><font > 当应用程序响应File->Open、File-Save和File-Save As命令时,应用程序框架都会通过调用CDocument成员函数(对于File->Open调用OnOpenDocument,对于File->Save和File->Save
As调用OnSaveDocument)创建CFile对象,并以适当的方式打开文件,对于File->Open是打开文件并读,对于Save和SaveAs是打开文件并写。然后框架会自动把文件对象连接到一个CArchive对象上,并设置CArchive的读写方式。</font></p>
<p><font > 在Editor的Serialize()函数体内,我们看到CArchive对象有一个IsStoring()成员函数。该成员函数告诉串行化函数是需要写入还是读取串行数据。如果数据要写入(Save或Save
As),IsStoring()返回布尔值TRUE;如果数据是被读取,则返回FALSE。</font></p>
<p><font > 现在添加串行化操作代码,实现编辑器文档的读写功能。修改后的Serialize()函数形式如清单7.6。</font></p>
<p><font >清单7.6 CEditorDoc的串行化方法</font></p>
<p><font >/////////////////////////////////////////////////////////////////////////////</font></p>
<p><font >// CEditorDoc serialization</font></p>
<p><font >void
CEditorDoc::Serialize(CArchive& ar)</font></p>
<p><font >{</font></p>
<p><font >CString s("");</font></p>
<p><font >int nCount=0;</font></p>
<p><font >CString item("");</font></p>
<p><font >if (ar.IsStoring())</font></p>
<p><font >{</font></p>
<p><font >POSITION pos;</font></p>
<p><font >pos=lines.GetHeadPosition();</font></p>
<p><font >if(pos==NULL)</font></p>
<p><font >{</font></p>
<p><font >return;</font></p>
<p><font >}</font></p>
<p><font >while(pos!=NULL)</font></p>
<p><font >{</font></p>
<p><font >item=lines.GetNext(pos);</font></p>
<p><font >ar<<item;</font></p>
<p><font >item.Empty();//clear the line buffer</font></p>
<p><font >}</font></p>
<p><font >}</font></p>
<p><font >else</font></p>
<p><font >{</font></p>
<p><font >// TODO: add loading code here</font></p>
<p><font >while(1)</font></p>
<p><font >{</font></p>
<p><font >try{</font></p>
<p><font >ar>>item;</font></p>
<p><font >lines.AddTail(item);</font></p>
<p><font >nCount++;</font></p>
<p><font >}</font></p>
<p><font >catch(CArchiveException *e)</font></p>
<p><font >{</font></p>
<p><font >if(e->m_cause!=CArchiveException::endOfFile)</font></p>
<p><font >{</font></p>
<p><font >TRACE0("Unknown exception loading
file!\n");</font></p>
<p><font >throw;</font></p>
<p><font >}else</font></p>
<p><font >{</font></p>
<p><font >TRACE0("End of file
reached...\n");</font></p>
<p><font >e->Delete();</font></p>
<p><font >}</font></p>
<p><font >break;</font></p>
<p><font >}</font></p>
<p><font >}</font></p>
<p><font >nLineNum=nCount;</font></p>
<p><font >}</font></p>
<p><font >}</font></p>
<p><font > 在If子句中,从字符串链表中逐行读取字符串,然后通过调用CArchive对象的<<操作符,将文本行写入ar对象中。在else子句中,从CArchive对象逐一读入字符串对象,然后加入到链表中。由于在Serialize()函数的载入文档调用之前,框架已经调用CDocument的DeleteContents()成员函数作好了清理工作,这里不必再重复清理字符串链表。在载入字符串对象的同时,统计了字符串的个数即文本行数。由于这里使用CString的串行化,因此获得的文件不同于普通的文本文件。</font></p>
<p><font > 文档串行化与一般文件处理方式最大的不同在于:在串行化中,对象本身对读和写负责。在上面的例子中,CArchive并不知道也不需要知道CString类的文本行内部数据结构,它只是调用CString类的串行化方法实现对象到文件的读写操作,也就是说,实际完成读写操作的是CString类,CArchive只是对象到CFile类的对象的一个中介。而文档的串行化正是通过调用文档中需要保存的各个对象的串行化方法来完成的。这几个对象的关系如图7-8所示。这里的对象必须是MFC对象,如果想让自己设计的对象也具有串行化能力,就必须定制该对象的串行化方法。有关定制串行化对象的技术在后面再作详细介绍。</font></p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -