1.使用CArchive类对文件进行读写操作
(1)MFC提供的CArchive类没有基类,可以用它将对象数据保存到永久设备上,这种让对象数据持久性的过程就称为串行化(或称为序列化)
(2)在程序中使用CArchive对象时,首先要在创建CArchive对象之前必须先创建一个CFile类或其派生类对象,并且因为存档对象既可以用来加载数据,也可以用来保存数据,所以要确保这个CFile类对象的打开方式与该存档对象的加载/保存状态相一致,当构造了一个CArchive对象后,就可以将它与一个代表某个打开文件的CFile类对象或其派生类对象相关联(一个文件只能与一个活动的存档对象相关联)
(3)CArchive对象不仅可以处理基本类型的数据,还可以处理CObject类的派生类对象,在CArchive类中重载了>>和<<操作符
(4)构造函数:CArchive(CFile* pFile,UINT nMode,int nBufSize = 4096,void* lpBuf = NULL);
// pFile指向文件对象的指针,该文件对象是持久数据的最终来源或目的地
//nMode指定对象是被用来加载的,还是用来保存的标识
// nBufSize指定内部文件缓冲区的大小(字节)
//可选指针,指向用户提供的大小为nBuffSize的缓冲区,如果未指定这个参数,那么存档对象将从应用程序的局部堆中分配一块缓冲区,并且该对象销毁时将释放这块内存
(5)示例:CFile file(“1.txt”,CFile::modeCreate|CFile::modeWrite);
CArchive ar(&file,CArchive::store);
int i = 4;
char ch = ‘a’;
CString str(“aaa”);
Ar<<i<<ch<<str;
2.MFC框架程序提供的文件新建功能
(1)CGraphicDoc类中OnNewDocument函数,它是由框架调用的一个虚函数
(2)程序启动时,会建立一个文档,文档的默认标题为“无标题”,我们可以在OnNewDocument这个函数中设置文档的标题:CDocument类中的成员函数:SetTitle
(3)IDR_MAINFRAME字符串资源也可以用来改变文档的标题,在CGraphicApp类的InitInstance函数中,将字符串资源传递给单文档模板对象。
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,//这里IDR_MAINFRAME资源不仅表示字符串资源,还表示菜单、图标等资源
RUNTIME_CLASS(CGraphicDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CGraphicView));
AddDocTemplate(pDocTemplate);
在构造单文档模板对象时,将文档类、框架类、视类都作为参数传递给该对象,也就是通过单文档模板对象将这几个类关联在一起,接下类,程序调用AddDocTemplate函数将已构造的文档模板对象加入到应用程序的文档模板链中,这时就把IDR_MAINFRAME字符串传递给应用程序的框架了
对IDR_MAINFRAME字符串资源,我们可以通过文档模板类的成员函数GetDocString来获取各个字串
(4)OnNewDocument函数的调用过程
OnNewDocument函数是文件新建功能的一部分(程序启动时,或单击【新建】菜单项都会调用此函数)在新建菜单项的响应函数中间接调用到此函数
程序启动时OnNewDocument函数的调用流程:
程序启动——CWinApp::OnFileNew(m_pDocManager->OnFileNew())——CDocManager::OnFileNew(pTemplate->OpenDocumentFile(NULL))——CSingleDocTemplate::OpenDocumentFile(CreateNewDocument—CreateNewFrame—pDocument->OnNewDocument)——CGraphicDoc::OnNewDocument
单文档应用程序单击文件新建菜单后OnNewDocument函数的调用流程:
菜单命令——CWinApp::OnFileNew(m_pDocManager->OnFileNew())——CDocManager::OnFileNew(pTemplate->OpenDocumentFile(NULL))——CSingleDocTemplate::OpenDocumentFile(pDocument->OnNewDocument)——CGraphicDoc::OnNewDocument
(5)文档管理器利用指针链表来维护文档模板的指针,文档模板管理文档类、框架类和视类对象
MFC提供的文档/视图结构的特点:每有一份文档产生时,总是会产生一个文档类对象、框架类对象和视类对象,它们三位一体来为这份文档服务
3.文档串行化
(1)CGraphicDoc类的Serialize函数,该函数有一个参数,类型是CArchive引用类型
在此函数中,首先判断ar的当前状态,如果是存储状态,则在其后增加相应的存储代码,否则在else子块中增加加载数据的代码
在使用【保存】和【打开】菜单项时会进入这个函数,这个函数就是文档类提供的用来保存和加载数据的函数,我们可以利用其参数提供的CArchive对象来保存或加载我们自己的数据
(2)文档/视图结构中,文档类负责管理数据,提供对数据的保存和加载,视类负责显示数据,为用户提供编辑数据和修改数据的功能
(3)MFC框架对Serialize函数的调用过程
文件打开命令——CWinApp::OnFileOpen(m_pDocManager->OnFileOpen())—CDocManager::OnFileOpen(调用DoPromptFileName函数显示文件打开对话框—AfxGetApp()->OpenDocumentFile)——CWinApp::OpenDocumentFile(m_pDocManager->OpenDocumentFile)——CDocManager::OpenDocumentFile(pTemplate->MatchDocType(szPath,pOpenDocument)—pBestTemplate->OpenDocumentFile)——CSingleDocTemplate::OpenDocumentFile(pDocument->
OnOpenDoument)——CDocument::OnOpenDocument(构造一个CFile::modeRead模式的CFile对象—构造一个CArchive::load模式的CArchive对象—Serialize(loadArchive))——CGraphicDoc::Serialize
当保存一个文件后再次打开这个文件不会调用Serialize函数,因为运行到CDocManager::OpenDocumentFile时,因为pOpenDocument这个指针有值了,所以这个函数直接返回pOpenDocument这个文档类指针,这个函数就结束了,不再往下执行。
(4)对于多文档程序来说,每打开一个文件都回构造一个新的文档对象,对单文档来说,文档对象本身并不会销毁,它只是将数据清空,然后与新的文件相关联
(5)【新建】【打开】菜单项的命令响应函数都在CWinApp类中,CWinApp有一个成员变量m_pDocManager,是指向CDocManager对象的指针,也就是说,CWinApp负责管理文档管理器,而后者有一个文档模板指针链表:m_templateList,用来保存文档模板的指针,即文档管理器负责管理文档模板,而后者又用来管理文档类、框架类和视类,始终让这三个对象三位一体,一起为文档服务
4.可串行化的类
(1)如果要用CArchive类保存一个对象的话,那么这个对象的类必须支持串行化,一个可串行化的类通常都有一个Serialize成员函数
(2)使一个类可串行化:
1.从CObject或其派生类派生
2.重写Serialize成员函数
3.使用DECLARE_SERIAL宏(在类声明中),声明形式:DECLARE_SERIAL(class_name)
4.定义不带参数的构造函数
5.为类在实现文件中使用IMPLEMENT_SERIAL宏,该宏的声明如下:
IMPLEMENT_SERIAL(class_name,base_class_name,wSchema)//类名,基类名,版本号
(3)利用可串行化类的Serialize函数保存和加载对象
1.集合类CObArray,该类用法和CPtrList相似,只是在添加元素时添加的时CObject指针 m_obArray.Add(pGraph);
2.在文档类中想要访问视类的对象,首先要获得视类对象的指针,对于一个文档类来说,可以有多个视类对象与之相关,但对于一个视类对象来说,它只能与一个文档类对象相关,为了获得与文档对象相关的视类对象,首先要通过CDocument类的成员函数GetFirstViewPosition()获得与该文档对象相关的视类链表中第一个视类对象的位置,然后通过GetNextView获得当前位置所指示的视类对象指针
函数原型:virtual POSITION GetFirstViewPosition() const;
函数原型:virtual CView* GetNextView(POSITION& rPosition) const;
//该函数调用之后,将返回这个位置所标识的视类对象指针,然后通过rPosition参数返回下
//一个视类对象的位置,于是不断调用GetNextView函数,就可以得到与文档类对象相关的
//每一个视类对象,如果到了视类链表的末尾,rPosition的值就会被置为NULL
3.有了视类对象的指针,就可以利用此指针访问视类对象的成员变量了
4. 利用视类的成员变量CObArray m_obArray(保存obArray中存储的对象(可串行化类的对象)和将对象加载到obArray中)
5.版本号:在利用文档类的Serialize函数保存一个可串行化类的对象时,实际上是利用对象本身的Serialize函数来完成的,因此,对象需要保存和读取的数据都在需要在该对象的Serialize函数中确定,这就要求在设计可串行化类的时候,在其内部确定需要串行化的数据,这就是可串行化类的内部实现机制
6.利用CObArray类对串行化的支持保存和加载数据
(1)由于CObArray类支持串行化,该类也有一个Serialize函数,CGraphicDoc对象的Serialize函数是由框架调用的,在该函数中,我们可以直接用它的参数ar(CArchive类型)传递给CObArray对象的Serialize函数(注意:一定要把这句调用代码放在if/else语句外面)pView->m_obArray.Serialize(ar);
(2)CObArray类的Serialize函数利用一个for循环将数组中的所有元素依次写入到文件中
(3)若将CObArray定义到文档类,在视类中访问文档类的成员,首先要得到文档类的指针,在视类中有一个GetDocument函数,这个函数就是视类为我们准备的可以获取文档类指针的函数,它可以直接获得指向文档类对象的指针。
5.文档对象的销毁
(1)以上的程序中,我们在OnLButtonUp函数中在堆上为CGraph对象分配了内存,但没有释放(画完一个图形时,新建一个对象)当我们新建一个文档或打开一个文档时,程序文档对象所保存的数据要被销毁,然后再与一个新的文档相关联,然而对于在堆上分配的内存,必须由程序员自己去释放
(2)因为文件打开和文件新建都会调用DeleteContents函数,所以在这个函数中释放文档对象在堆上分配的内存比较合适,DeleteContents函数是一个虚函数,主要由框架类调用,用来删除文档的数据,同时并不销毁CDocument对象本身,它是在文档将要被销毁之前被调用,它也会在该文档对象在重复使用之前被调用,以确保文档是空的。对单文档程序来说,文档对象是被重复使用的,所以在文档对象被重复使用之前,应释放已分配的内存。
(3)为CGraphicDoc类添加DeleteContents虚函数,并添加代码
(4)利用CObArray类的RemoveAt函数删除元素时,它会下移在这个元素之上的所有元素,并减少这个数组的上界