• 1. 第8章文档和视图 8.1文档模板 8.2文档序列化 8.3视图及视图类 8.4文档视图结构
  • 2. 8.1文档模板 8.1.1文档模板类 文档应用程序框架结构是在程序运行一开始构造的。 多文档模板类CMultiDocTemplate的构造函数也有相同的定义。代码: BOOL CEx_MDIApp::InitInstance() { … CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_EX_MDITYPE, // 资源ID RUNTIME_CLASS(CEx_MDIDoc), // 文档类 RUNTIME_CLASS(CChildFrame), // MDI文档窗口类 RUNTIME_CLASS(CEx_MDIView)); // 视图类 AddDocTemplate(pDocTemplate); // 创建主框架窗口 CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; … return TRUE; }
  • 3. 8.1文档模板8.1.2文档模板字串资源 String Table(字符串)资源列表中也有一个IDR_MAINFRAME项,用来标识文档类型、标题等内容,称为“文档模板字串资源”。内容如下: Ex_SDI\n\nEx_SDI\n\n\nExSDI.Document\nEx_SDI Document IDR_MAINFRAME标识的字符串分成了一些以“\n”结尾的子串,含义如表。 文档模板字串资源内容既可直接通过字串资源编辑器进行修改,也可以在文档应用程序创建向导的第四步中,通过“Advanced Options”对话框中的“Document Template Strings”页面来指定,如图。
  • 4. 8.1文档模板8.1.3使用多个文档类型 [例Ex_MDIDemo] 多种文档类型示例。 (1)   用MFC AppWizard创建一个默认的多文档应用程序项目Ex_MDIDemo。 (2) 打开项目工作区窗口中String Table,双击String Table,如图。 (3) 双击IDR_MAINFRAME列表项,将其标题修改为“多个文档类型实例”,如图。 (4)  双击IDR_EX_MDITYPE列表项,在字符串属性对话框中,将其内容修改为: \nPicture\nMDIDemo图片\n图片文件(*.bmp)\n.bmp\nExMDIDemo.Document\nEx_MDI Document
  • 5. 8.1文档模板(5)拖动字符串表右边的滚动块,双击最后的空行,在字符串属性对话框中将ID设为IDR_OTHERTYPE,标题内容设为: \nTxt\nMDIDemo文本\n文本文件(*.txt, *.cpp, *.h)\n.txt;*.cpp;*.h\nExMDIDemo.Document\n Ex_MDI Document 如图。IDR_OTHERTYPE标识符后面的“=130”是将该标识符值设为130。关闭属性对话框,标识为IDR_OTHERTYPE字串项就添加到字串表中。 (6)按快捷键Ctrl+W,单击[Add Class],在Name框中输入类名COtherDoc,在Base class组合框中选择基类CDocument,如图。
  • 6. 8.1文档模板(7)  单击[OK]。再添加一个新的视图类COtherView,基类为CView。单击[确定],关闭MFC ClassWizard对话框。 (8)    修改CEx_MDIDemoApp::InitInstance函数代码,如下所示: BOOL CEx_MDIDemoApp::InitInstance() { … CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_EX_MDITYPE, RUNTIME_CLASS(CEx_MDIDemoDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CEx_MDIDemoView)); AddDocTemplate(pDocTemplate); pDocTemplate = new CMultiDocTemplate( IDR_OTHERTYPE, // 指定新的资源 RUNTIME_CLASS(COtherDoc), // 指定新的文档类 RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(COtherView)); // 指定新的视图类 AddDocTemplate(pDocTemplate); … return TRUE; }
  • 7. 8.1文档模板(9)在Ex_MDIDemo.cpp的开始处,添加包含前面创建的两个派生类的头文件: #include "Ex_MDIDemoDoc.h" #include "Ex_MDIDemoView.h“ #include "OtherDoc.h“ #include "OtherView.h“ (10) 编译运行并测试。在程序运行的一开始弹出文档类型的“新建”对话框,如图7.6所示。选择“MDIDemo图片”,单击[确定],出现CEx_MDIDemo主框架窗口界面,同时出现标题为 “Picture1”的文档窗口。选择“文件” “新建”菜单,又会出现如图7.6所示的“新建”对话框,选择“MDIDemo文本”,单击[确定]后,出现标题为 “Txt1”的文档窗口。结果如图7.7所示。选择“文件” “打开”菜单,出现如图7.8所示的文件打开对话框。 需要说明的是: (1)如果在上例程序中再添加图标和菜单资源,并使资源标识设为IDR_OTHERTYPE,则当创建“MDIDemo文本”文档类型后,则程序会自动使用新的图标和菜单资源。 (2)单文档应用程序也可以有多个文档类型,它的实现方法与多文档类似,也是通过添加文档模板来实现的,只不过每次只能在文档窗口(视图)中显示一个文档。
  • 8. 8.1文档模板 图7.6 文档类型新建对话框 图7.7 多类型文档窗口显示 图7.8 文件打开对话框中的文件类型
  • 9. 8.2文档序列化 8.2.1文档序列化过程 创建空文档 (1)  构造文档对象,但并不从磁盘中读数据。 (2)  构造主框架类CMainFrame的对象,并创建该主框架窗口,但不显示。 (3)  构造视图对象,并创建视图窗口,也不显示。 (4) 通过内部机制,使文档、主框架和视图“对象”之间“真正”建立联系。注意,AddDocTemplate函数建立的是“类”之间的联系。 (5) 调用文档对象的CDocument::OnNewDocument虚函数,并调用CDocument:: DeleteContents虚函数来清除文档对象的内容。 (6) 调用视图对象的CView::OnInitialUpdate虚函数对视图进行初始化操作。 (7) 调用框架对象的CFrameWnd::ActiveFrame虚函数。 文档、主框架以及视图对象仅被创建一次,这些对象在整个运行过程中都有效。CWinApp::OnFileNew函数被InitInstance函数所调用。选择“文件(File)”菜单中的“新建(New)”时,CWinApp::OnFileNew也被调用,这种情况下不再创建文档、主框架以及视图对象,上述过程的最后三个步骤仍然会被执行。
  • 10. 8.2文档序列化打开文档 MFC AppWizard创建应用程序时,自动将“文件”菜单中的“打开”命令映射到CWinApp的OnFileOpen成员函数。可以从应用类(.cpp)的消息入口处得到验证: BEGIN_MESSAGE_MAP(CEx_SDIApp, CWinApp) …… ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) // Standard print setup command ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP() OnFileOpen函数还会进一步完成下列工作: (1)  弹出通用文件“打开”对话框,供用户选择一个文档。 (2) 文档指定后,调用文档对象的CDocument:: OnOpenDocument虚函数。函数将打开文档,调用DeleteContents清除文档,创建CArchive对象,调用Serialize函数。 (3)  调用视图对象的CView::OnInitialUpdate虚函数。 可以选择最近使用过的文件列表来打开相应的文档。在程序运行过程中,系统会记录4个最近使用过的文件,并将文件名保存在Windows的注册表中。当每次启动应用程序时,应用程序都会最近使用过的文件名称显示在“文件(File)”菜单中。
  • 11. 8.2文档序列化保存文档 OnFileSave函数会进一步完成下列工作: (1)  弹出通用文件“保存”对话框,让用户提供一个文件名。 (2) 调用文档对象的CDocument::OnSaveDocument虚函数,接着又自动调用Serialize函数,将CArchive对象的内容保存在文档中。 l 只有在保存文档之前还没有存过盘或读取的文档是“只读”的,OnFileSave函数才会弹出通用“保存”对话框。否则,只执行第二步。 l “文件(File)”菜单中还有一个“另存为(Save As)”命令,它是与文档类CDocument的OnFileSaveAs函数相关联。不管文档有没有保存过,OnFileSaveAs都会执行上述两个步骤。 关闭文档 试图关闭文档时,应用程序会根据对文档的修改与否来完成下列任务: (1)  若文档内容已被修改,则弹出对话框,询问用户是否需要将文档保存。 (2) 调用CDocument::OnCloseDocument虚函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类t的DeleteContents清除文档数据。 MFC应用程序通过CDocument的protected类型成员变量m_bModified的逻辑值来判断是否对文档进行修改。用户可以通过CDocument的SetModifiedFlag成员函数来设置或通过IsModified成员函数来访问m_bModified的逻辑值。文档创建、从磁盘中读出及文档存盘时,文档的这个标记就被置为FALSE(假);文档数据被修改时,必须使用SetModifiedFlag函数将该标记置为TRUE。
  • 12. 8.2文档序列化8.2.2文档序列化操作 打开和保存文档时,自动调用Serialize函数。创建文档框架时已在文档类中重载了Serialize函数。例如,在Ex_SDI单文档的文档类中的默认代码: void CEx_SDIDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) // 当文档数据需要存盘时 { // TODO: add storing code here } else // 当文档数据需要读取时 { // TODO: add loading code here } } CArchive类对文件数据进行缓存,还保存一个标识文档是存入还是载入内部标记。每次只能有一个活动的存档与ar相连。CArchive类可以简化文件操作,提供“<<”和“>>”运算符,用于向文件写入简单的数据类型以及从文件中读取它们,如表。
  • 13. 8.2文档序列化 CArchive类提供成员函数ReadString和WriteString从一个文件对象中读写一行文本,原型: Bool ReadString(CString& rString ); LPTSTR ReadString( LPTSTR lpsz, UINT nMax ); void WriteString( LPCTSTR lpsz ); [例Ex_SDIArchive] 一个简单的文档序列化示例。 (1)用MFC AppWizard创建一个默认的单文档应用程序Ex_SDIArchive。 (2) 打开String Table资源,文档模板字串资源IDR_MAINFRAME内容修改: 文档序列化操作\n\n\n自定义文件(*.my)\n.my\nExSDIArchive.Document\nEx_SDI Document (3)  为CEx_SDIArchiveDoc类添加下列成员变量: public: char m_chArchive[100]; // 读写数据时使用 CString m_strArchive; // 读写数据时使用 BOOL m_bIsMyDoc; // 用于判断文档 (4)   在CEx_SDIArchiveDoc类构造函数中添加下列代码: CEx_SDIArchiveDoc::CEx_SDIArchiveDoc() { m_bIsMyDoc = FALSE; }
  • 14. 8.2文档序列化(5) 在CEx_SDIArchiveDoc::OnNewDocument函数中添加下列代码: BOOL CEx_SDIArchiveDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; strcpy(m_chArchive, "&这是一个用于测试文档的内容!"); m_strArchive = "这是一行文本!"; m_bIsMyDoc = TRUE; return TRUE; }
  • 15. 8.2文档序列化(6)   在CEx_SDIArchiveDoc::Serialize函数中添加下列代码: void CEx_SDIArchiveDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { if (m_bIsMyDoc) // 是自己的文档 { for (int i=0; i>m_chArchive[0]; // 读取文档首字符 if (m_chArchive[0] == '&') // 是自己的文档 { for (int i=1; i>m_chArchive[i]; ar.ReadString( m_strArchive ); CString str; str.Format("%s%s",m_chArchive,m_strArchive); AfxMessageBox(str); m_bIsMyDoc = TRUE; }else // 不是自己的文档 { m_bIsMyDoc = FALSE; AfxMessageBox(“打开的文档无效!”); }}}
  • 16. 8.2文档序列化(7) 将文档模板字串资源IDR_MAINFRAME内容修改如下: 文档序列化操作\n\n\n自定义文件(*.my)\n.my\nExSDIArchive.Document\nEx_SDI Document (8)编译运行并测试。程序运行后,我们选择“文件”“另存为”菜单,指定一个文档名1.my,然后选择“文件”“新建”菜单,再打开该文档,结果就会弹出对话框,显示该文档的内容,如图。
  • 17. 8.2文档序列化8.2.3使用简单数组集合类 简单数组集合类的构造及元素的添加 简单数组集合类构造均是使用各自的构造函数,原型: CByteArray CByteArray( ); CDWordArray CDWordArray( ); CObArray CObArray( ); CPtrArray CPtrArray( ); CStringArray CStringArray( ); CUIntArray CUIntArray( ); CWordArray CWordArray( ); 下面的代码说明了简单数组集合类的两种构造方法: CObArray array; // 使用默认的内存块大小 CObArray* pArray = new CObArray; // 使用堆内存中的默认的内存块大小 使用简单数组集合类之前最好调用成员函数SetSize 设置此数组的大小,原型: void SetSize( int nNewSize, int nGrowBy = -1 ); int GetSize( ) const; 向简单数组集合类添加一个元素,使用成员函数Add和Append,原型: int Add( CObject* newElement ); int Append( const CObArray& src );
  • 18. 8.2文档序列化访问简单数组集合类的元素 简单数组集合类元素的访问既可用GetAt函数,也可用“[]”操作符,例如: // CObArray::operator []示例 CObArray array; CAge* pa; // CAge是一个用户类 array.Add( new CAge( 21 ) ); // 添加一个元素 array.Add( new CAge( 40 ) ); // 再添加一个元素 pa = (CAge*)array[0]; // 获取元素0 ASSERT( *pa == CAge( 21 ) ); array[0] = new CAge( 30 ); // 替换元素0; ASSERT( *(CAge*) array[0] == CAge( 30 ) );   // CObArray::GetAt示例 CObArray array; array.Add( new CAge( 21 ) ); // 元素 0 array.Add( new CAge( 40 ) ); // 元素 1 ASSERT( *(CAge*) array.GetAt( 0 ) == CAge( 21 ) ); 删除简单数组集合类的元素 (1)    使用函数GetSize和整数下标值访问简单数组集合类中的元素。 (2)   若对象元素是在堆内存中创建的,则使用delete操作符删除每一个对象元素。 (3) 调用函数RemoveAll删除简单数组集合类中的所有元素。
  • 19. 8.2文档序列化[例Ex_Array] 读取文档数据并显示。 (1)   用MFC AppWizard创建一个默认的单文档应用程序Ex_Array。 (2)  添加CStringArray类型的成员变量m_strContents。 (3)   在CEx_ArrayDoc::Serialize函数中添加读取文档内容的代码: void CEx_ArrayDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) {} else{ CString str; m_strContents.RemoveAll(); while (ar.ReadString(str)) { m_strContents.Add(str); } } } (4)   在CEx_ArrayView::OnDraw中添加下列代码: void CEx_ArrayView::OnDraw(CDC* pDC) { CEx_ArrayDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); int y = 0; CString str; for (int i=0; im_strContents.GetSize(); i++) { str = pDoc->m_strContents.GetAt(i); pDC->TextOut( 0, y, str); y += 16;} }
  • 20. 8.2文档序列化(5)编译运行并测试,打开任意一个文本文件,如图。
  • 21. 8.2文档序列化8.2.4文档序列化实例 [例Ex_Student] 文档序列化。 创建一个SDI应用程序 添加用于学生成绩记录输入的对话框 用于学生成绩记录输入的对话框如图。这里要做的是如何将其复制过来,包括对话框类CInputDlg代码。
  • 22. 8.2文档序列化复制的步骤如下: (1)  将Ex_Student工作区切换到ResourceView页面,展开Dialog资源。 (2) 单击开发环境标准工具栏上的“打开”按钮(),打开Ex_CtrlSDI的资源文件Ex_CtrlSDI.rc,展开Dialog资源的所有项,选定IDD_INPUT对话框资源项,按住Ctrl键,将其拖放到Ex_Student项目的Dialog资源处。 (3)单击标准工具栏上的“打开”,选中InputDlg.h和InputDlg.cpp文件,按快捷键Ctrl+C,然后“打开”文件对话框的文件“查找范围”定位到本项目的文件夹中,按快捷键Ctrl+V,CInputDlg类的两个源代码文件就复制过来了。 (4)   关闭“打开”文件对话框,选择“工程”“添加工程”“Files”菜单,在弹出的对话框中选中刚才的两个源文件,单击[确定],CInputDlg类就添加到Ex_Student项目中。 (5)   打开InputDlg.cpp文件,将文件前面的包含语句进行修改,如下所示: #include "stdafx.h“ #include "Ex_Student.h" // 修改原来的#include "Ex_CtrlSDI.h" #include "InputDlg.h"
  • 23. 8.2文档序列化添加一个CStudent类并使该类可序列化 (1) 打开在Ex_StudentDoc.h文件,在class CEx_StudentDoc前添加下列代码: class CStudent : public CObject { CString strName; // 姓名 CString strNO; // 学号 float fScore1, fScore2, fScore3; // 三门成绩 float fAverage; // 平均成绩 DECLARE_SERIAL(CStudent) // 序列化声明 public: CStudent() {}; CStudent(CString name, CString id, float f1, float f2, float f3); void Serialize(CArchive &ar); void Display(int y, CDC *pDC); // 在坐标为(0,y)处显示数据 };
  • 24. 8.2文档序列化(2) 打开Ex_StudentDoc.cpp文件,在文件最后添加下列CStudent实现代码: CStudent::CStudent(CString name, CString id, float f1, float f2, float f3) { strName = name; strNO = id; fScore1 = f1; fScore2 = f2; fScore3 = f3; fAverage = (float)((f1 + f2 + f3)/3.0); } void CStudent::Display(int y, CDC *pDC) { CString str; str.Format("%s %s %f %f %f %f", strName, strNO, fScore1, fScore2, fScore3, fAverage); pDC->TextOut(0, y, str); } IMPLEMENT_SERIAL(CStudent, CObject, 1) // 序列化实现 void CStudent::Serialize(CArchive &ar) { if (ar.IsStoring()) ar<>strName>>strNO>>fScore1>>fScore2>>fScore3>>fAverage; }
  • 25. 8.2文档序列化添加并处理菜单项 (1)在菜单资源的主菜单中添加顶层菜单项“学生记录(&S)”,在该顶层菜单项中添加子菜单“添加(&A)”(ID_STUREC_ADD)。 (2)    用MFC ClassWizard为CEx_StudentDoc类添加处理菜单项ID_STUREC_ADD的COMMAND消息,并添加下列代码: void CEx_StudentDoc::OnSturecAdd() { CInputDlg dlg; if (IDOK == dlg.DoModal()) { // 添加记录 CStudent *pStudent = new CStudent(dlg.m_strName, dlg.m_strNO, dlg.m_fScore1, dlg.m_fScore2, dlg.m_fScore3); m_stuObArray.Add(pStudent); SetModifiedFlag(); // 设置文档更改标志 UpdateAllViews(NULL); // 更新视图 } } (3)    在CEx_StudentDoc.cpp文件的开始处,添加包含CInputDlg的头文件。 #include "InputDlg.h"
  • 26. 8.2文档序列化修改CEx_StudentDoc类代码 (1)    为CEx_StudentDoc类添加下列成员变量: public: CObArray m_stuObArray; (2) 添加成员函数CStudent* GetStudentAt(int nIndex),代码: CStudent * CEx_StudentDoc::GetStudentAt(int nIndex) { if ((nIndex < 0) || nIndex > m_stuObArray.GetUpperBound()) return 0; // 超界处理 return (CStudent *)m_stuObArray.GetAt(nIndex); } (3)  为CEx_StudentDoc类添加成员函数int GetAllRecNum(void),代码: int CEx_StudentDoc::GetAllRecNum() { return m_stuObArray.GetSize(); }
  • 27. 8.2文档序列化(4)    在CEx_StudentDoc类析构函数~CEx_StudentDoc添加下列代码: CEx_StudentDoc::~CEx_StudentDoc() { int nIndex = GetAllRecNum(); while (nIndex--) delete m_stuObArray.GetAt(nIndex); m_stuObArray.RemoveAll(); } (5)    在CEx_StudentDoc::Serialize函数中添加下列代码: void CEx_StudentDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { m_stuObArray.Serialize(ar); } else { m_stuObArray.Serialize(ar); } }
  • 28. 8.2文档序列化修改CEx_StudentView类代码 将CEx_StudentView::OnDraw代码修改如下: void CEx_StudentView::OnDraw(CDC* pDC) { CEx_StudentDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); int y = 0; for (int nIndex = 0; nIndex < pDoc->GetAllRecNum(); nIndex++) { pDoc->GetStudentAt(nIndex)->Display(y, pDC); y += 16; } } 修改文档的字串资源 打开文档的字串资源IDR_MAINFRAME,将其内容修改为: Ex_Student\nStudentRec\nEx_Stu\n记录文件(*.rec)\n.rec\nExStudent.Document\nEx_Stu Document
  • 29. 8.2文档序列化编译运行并测试 编译运行并测试,如图:
  • 30. 8.2文档序列化8.2.5使用CFile类 文件的打开和关闭 (1) 构造一个不带任何参数的CFile对象; (2) 调用成员函数Open并指定文件路径以及文件标志。 CFile类的Open函数原型如下: BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL ); 例如,下面的代码将显示如何用读写方式创建一个新文件: char* pszFileName = "c:\\test\\myfile.dat"; CFile myFile; CFileException fileException; if ( !myFile.Open( pszFileName, CFile::modeCreate | CFile::modeReadWrite ), &fileException ) { TRACE( "Can't open file %s, error = %u\n", pszFileName, fileException.m_cause ); } 使用Close关闭一个文件对象,若对象在堆内存中创建,需调用delete来删除它。
  • 31. 8.2文档序列化
  • 32. 8.2文档序列化文件的读写和定位 CFile类支持文件的读、写和定位操作。它们相关函数的原型如下: UINT Read( void* lpBuf, UINT nCount ); 此函数将文件中指定大小的数据读入指定的缓冲区,返回向缓冲区传输的字节数。这个返回值可能小于nCount。 void Write( const void* lpBuf, UINT nCount ); 此函数将缓冲区的数据写到文件中。参数lpBuf指定要写到文件中的数据缓冲区的指针,nCount表示从数据缓冲区传送的字节数。对于文本文件,每行的换行符也被计算在内。 LONG Seek( LONG lOff, UINT nFrom ); 此函数用来定位文件指针的位置,定位的位置是合法的,函数返回从文件开始的偏移量。否则,返回值是不定的且激活一个CFileException对象。参数lOff指定文件指针移动的字节数,nFrom表示指针移动方式。文件刚打开时,默认的文件指针位置为0,即文件的开始位置。 函数void SeekToBegin( )和DWORD SeekToEnd( )分别将文件指针移动到文件开始和结尾位置,对于后者还将返回文件的大小。
  • 33. 8.2文档序列化获取文件的有关信息 CFile 还支持获取文件状态,包括文件是否存在、创建与修改的日期和时间、逻辑大小和路径等。 BOOL GetStatus( CFileStatus& rStatus ) const; static BOOL PASCAL GetStatus( LPCTSTR lpszFileName, CFileStatus& rStatus ); 若指定文件的状态信息成功获得,该函数返回TRUE,否则返回FALSE。参数lpszFileName用来指定一个文件路径,这个路径可以是相对的或是绝对的,但不能是网络文件名。rStatus用来存放文件状态信息,它是一个CFileStatus结构类型,该结构具有下列成员: CTime m_ctime 文件创建日期和时间 CTime m_mtime 文件最后一次修改日期和时间 CTime m_atime 文件最后一次访问日期和时间 LONG m_size 文件的逻辑大小字节数,就像DOS命令中DIR所显示的大小 BYTE m_attribute 文件属性 char m_szFullName[_MAX_PATH] 文件名 static形式的GetStatus函数将获得指定文件名的文件状态,并将文件名复制至m_szFullName中。该函数仅获取文件状态,并没有真正打开文件,这对于测试一个文件的存在性是非常有用的。
  • 34. 8.2文档序列化CFile和CArchive类之间的关联 可以将一个外部磁盘文件和一个CArchive 对象关联起来。例如: CFile theFile; theFile.Open(..., CFile::modeWrite); CArchive archive(&theFile, CArchive::store); CArchive构造函数的原型如下: CArchive( CFile* pFile, UINT nMode, int nBufSize = 4096, void* lpBuf = NULL ); 参数pFile指定与之关联的文件指针。nBufSize表示内部文件的缓冲区大小。lpBuf表示自定义的缓冲区指针,为NULL,表示缓冲区建立在堆内存中,对象清除时,缓冲区内存也被释放;若指明用户缓冲区,对象消除时,缓冲区内存不会被释放。nMode文档是存入还是读取,可以是CArchive::load(读取数据)、CArchive::store(存入数据)或CArchive::bNoFlushOnDelete(析构函数被调用时,避免文档自动调用Flush 。若设置这个标志,则必须在析构函数被调用之前调用Close。否则文件数据将被破坏)。 也可以将一个CArchive 对象与CFile类指针相关联,如下面的代码: const CFile* fp = ar.GetFile();
  • 35. 8.3视图及视图类 视图是框架窗口的子窗口,它与文档紧密相联,是用户与文档之间的交互接口。视图不仅可以响应各种类型的输入,而且能实现文档的打印和打印预览。 MFC中的CView类及其它的派生类封装了视图的各种不同的功能,为实现最新的Windows特性提供了很大的便利。如表。
  • 36. 8.3视图及视图类CEditView类 [例Ex_Edit] 创建一个基于CEditView类的单文档应用程序。 (1) 选择“文件”“新建”,选择“工程”标签,选择MFC AppWizard(exe)的项目类型,指定项目工作文件夹位置,输入项目名Ex_Edit,单击[确定]。 (2)    在向导的第一步中,将应用程序类型选为“单个文档”(SDI)。 (3) 单击[下一步],直到出现向导的第六步,将CEx_EditView的基类选为CEditView,如图。 (4)单击[完成]按钮,编译运行,打开一个文档,结果如图8.15所示。
  • 37. 8.3视图及视图类CRichEditView类 使用了复合文本编辑控件。被设计成与CRichEditDoc和CRichEditCntrItem类一起使用,可实现一个完整的ActiveX包容器应用程序。 CFormView类 具有许多无模式对话框的特点。CFormView的派生类也和相应的对话框资源相联系,也支持对话框数据交换和对话框数据确认(DDX和DDV)。 是所有表单的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。 创建表单应用程序的基本方法还可以通过相关菜单命令在文档应用程序中自动插入一个表单。
  • 38. 8.3视图及视图类[例Ex_Form] 在单文档应用程序中添加表单视图。 (1)   用MFC AppWizard创建一个默认的的单文档应用程序Ex_Form。 (2) 将工作区切换到ClassView页面,在顶层项名称上右击鼠标按钮。选择“New Form”命令,或者在主菜单中选择“插入”“新建形式”,在Name框中输入CTextView,如图。 在“New Form”对话框中有创建文档类的操作,单击[New]可以重新指定要创建的文档类,单击[Change]可更改要添加的文档模板字串资源。
  • 39. 8.3视图及视图类 (3)单击[OK]。如图。右边是表单资源编辑器。 (4)右击表单模板,选择“属性”,将字体设置为“宋体,9号”。 (5)删除原来的静态文本控件,添加一个编辑框,选中Multiline(多行)、Horizontal scroll(水平滚动条)、Vertical scroll(垂直滚动条)和Auto VScroll(自动垂直滚动)属性。编辑框的标识为IDC_EDIT1,为其创建一个CString控件变量m_strText。
  • 40. 8.3视图及视图类(6)  为CEx_FormDoc类添加一个成员变量CString m_strContent。 (7)  在CEx_FormDoc::Serialize函数中添加下列代码: void CEx_FormDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) {} else { CString str; m_strContent.Empty(); // 清空字符串变量内容; while (ar.ReadString(str)) { m_strContent = m_strContent + str; m_strContent = m_strContent + "\r\n"; // 在每行文本未尾添加回车换行 } } } (8)为CTextView类添加OnUpdate函数的重载映射,文档更新后,自动调用OnUpdate函数。在OnUpdate函数中添加下列代码: void CTextView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { CEx_FormDoc* pDoc = (CEx_FormDoc*)GetDocument(); m_strText = pDoc->m_strContent; UpdateData(FALSE); }
  • 41. 8.3视图及视图类(9)   在TextView.cpp文件前面添加CEx_FormDoc类包含文件: #include "Ex_Form.h" #include "TextView.h“ #include "Ex_FormDoc.h" (10) 表单添加后,MFC会自动在CEx_FormApp::InitInstance函数中添加一个单文档模板代码,这样该单文档应用程序就有两个文档类型。本例中只需一个文档模板类型,将InitInstance函数修改如下: BOOL CEx_FormApp::InitInstance() { // 前面的这段文档模板代码删除 AfxEnableControlContainer(); … CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME, RUNTIME_CLASS(CEx_FormDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CTextView)); // 修改成添加的表单视图类 AddDocTemplate(pDocTemplate); … return TRUE; }
  • 42. 8.3视图及视图类(11) 编译运行并测试,如图。 (12) 为CTextView类添加WM_SIZE的消息映射,添加代码: void CTextView::OnSize(UINT nType, int cx, int cy) { CFormView::OnSize(nType, cx, cy); CWnd* pWnd = GetDlgItem(IDC_EDIT1); // 获取编辑框窗口指针 if (pWnd) // 若窗口指针有效 pWnd->SetWindowPos( NULL,0,0,cx,cy,SWP_NOZORDER); } (13)再次编译运行并测试,如图。
  • 43. 8.3视图及视图类CHtmlView 类 是在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址,可以作为本地文件和网络文件系统的窗口,支持超级链接、统一资源定位(URL)导航器并维护历史列表等。 CScrollView类 能直接支持视图的滚动操作,能管理视口的大小和映射模式,能响应滚动条消息、键盘消息以及鼠标滚轮消息。 列表控件和列表视图 列表控件可以用 “大图标”、“小图标”、“列表视图”或“报表视图”等四种不同的方式来显示一组信息,如图。
  • 44. 8.3视图及视图类树控件和树视图 树视图类CTreeView是将树控件集成到MFC文档视图结构中,从而可以在视图的客户区中用“树”的方式显示带图标的项(节点)。 与列表控件不同的是,在树控件的初始状态下只显示少量的顶层信息,这样有利于决定树的哪一部分需要展开,可从视图中看到节点之间的层次关系。 树控件由父节点和子节点组成。位于某一节点之下的节点称为“子”节点,位于子节点之上的节点称为该节点的“父”节点。位于树的顶层或根部的节点称为“根”节点。 CTreeView类简化了CTreeCtrl类的使用,是按照MFC文档视图结构封装了树控件的功能。CTreeView类提供的成员函数GetTreeCtrl可使我们从CTreeView中得到封装的CTreeCtrl对象。
  • 45. 8.4文档视图结构 8.4.1文档与视图的相互作用 CView::GetDocument函数 视图对象包含的GetDocument函数允许应用程序由视图得到与之相关联的文档。GetDocument函数返回的是指向文档的指针。 MFC AppWizard产生CView的用户派生类时,同时也创建一个安全类型的GetDocument函数,返回的是指向用户派生文档类的指针。该函数是一个内联(inline)函数,类似于下面的代码形式: CMyDoc* CMyView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMyDoc))); // “断言”m_pDocument指针可以指向的CMyDoc类是一个RUNTIME_CLASS类型 return (CMyDoc*)m_pDocument; } 编译器在视图类代码中遇到对GetDocument函数的调用时,它执行的实际上是派生类中GetDocument函数代码。 CDocument::UpdateAllViews函数 UpdateAllViews函数原型如下: void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ); 如果参数pSender指向某个特定的视图对象,那么除了该指定的视图之外,文档的所有其他视图的OnUpdate函数都会被调用。
  • 46. 8.4文档视图结构CView::OnUpdate函数 应用程序调用CDocument::UpdateAllViews函数时,应用程序框架就会相应地调用各视图的OnUpdate函数,原型: virtual void OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint ); 参数pSender表示文档被更改的所关联的视图类指针。 默认的OnUpdate函数(lHint = 0, pHint = NULL)使得整个窗口矩形无效。如果用户想要视图的某部分无效,那么用户就要定义相关的提示(hint)参数给出准确的无效区域;lHint可表示任何内容,pHint可传递从CObject派生的类指针;还可用CWnd::InvalidateRect来代替上述方法。 hint机制主要用来传递更新视图时所需的一些相关数据或其他信息,例如将文档的CPoint数据传给所有的视图类,则有下列语句: GetDocument()->UpdateAllViews(NULL, 1, (CObject *)&m_ptDraw);
  • 47. 8.4文档视图结构CView::OnInitialUpdate函数 应用程序被启动时,或“文件”菜单中选择了“新建”或“打开”时,CView虚函数会被自动调用。该函数除了调用无提示参数的OnUpdate函数之外,没有其他任何操作。 用户可以重载此函数对文档所需信息进行初始化操作。例如,如果应用程序中的文档大小是固定的,那么就可以在此重载函数中根据文档大小设置视图滚动范围;如果应用程序中的文档大小是动态的,那么就可在文档每次改变时调用OnUpdate来更新视图的滚动范围。 CDocument::OnNewDocument函数 在文档应用程序中,“文件”菜单中选择“新建”命令时,框架将首先构造一个文档对象,然后调用该虚函数。这里是设置文档数据成员初始值的好地方,当然文档数据成员初始化处理还有其他的一些方法。
  • 48. 8.4文档视图结构8.4.2应用程序对象指针的互调 从文档类中获取视图对象指针 在文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的成员函数GetFirstViewPosition和GetNextView来定位相应的视图对象。 GetFirstViewPosition函数获得与文档类相关联的视图列表中第一个可见视图的位置,GetNextView函数获取指定视图位置的视图类指针,并将此视图位置移动到下一个位置,若没有下一个视图,则视图位置为NULL。原型: virtual POSITION GetFirstViewPosition( ) const; virtual CView* GetNextView( POSITION& rPosition ) const; 从视图类中获取文档对象和主框架对象指针 在视图类中获取文档对象指针只需调用视图类中的成员函数GetDocument。 函数CWnd::GetParentFrame可实现从视图类中获取主框架指针,原型: CFrameWnd* GetParentFrame( ) const; 该函数将获得父框架窗口指针,它在父窗口链中搜索,直到一个CFrameWnd(或其派生类)被找到为止。成功时返回一个CFrameWnd指针,否则返回NULL。
  • 49. 8.4文档视图结构在主框架类中获取视图对象指针 对单文档程序,只需调用CFrameWnd类的GetActiveView成员函数,原型: CView* GetActiveView( ) const; 在多文档应用程序中获取活动视图对象指针的正确方法是:先获得多文档应用程序的活动文档窗口,然后再获得与该活动文档窗口相关联的活动视图。代码: CMDIFrameWnd *pFrame = (CMDIFrameWnd*)AfxGetApp()->m_pMainWnd; // 获得MDI的活动子窗口 CMDIChildWnd *pChild = (CMDIChildWnd *) pFrame->GetActiveFrame(); // 或 CMDIChildWnd *pChild = pFrame->MDIGetActive(); // 获得与子窗口相关联的活动视图 CMyView *pView = (CMyView *) pChild->GetActiveView(); 在同一个应用程序的任何对象中,可通过全局函数AfxGetApp()来获得指向应用程序对象的指针。
  • 50. 8.4文档视图结构8.4.3切分窗口 静态切分和动态切分 窗口第一次被创建时,窗格就已经被切分好了,窗格的次序和数目不能再被改变,可以移动切分条来调整窗格的大小。每个窗格通常是不同的视图类。 “动态切分”窗口允许在任何时候对窗口进行切分,可以选择菜单项来对窗口切分,可以通过拖动滚动条中的切分块对窗口进行切分。 动态切分窗口中的窗格通常使用的是同一个视图类。切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。视图沿着某个方向被切分时,另一个新添加的视图对象被动态创建;视图沿着两个方向被切分时,新添加的三个视图对象则被动态创建。用户取消切分时,所有新添加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失为止。 无论是静态动态切分,创建时都要指定切分窗口中行和列的窗格最大数目。对于静态切分,窗格在初始时就按用户指定的最大数目划分好了;而对于动态切分窗口,窗口构造时,第一个窗格就被自动创建。动态切分窗口允许的最大窗格数目是2 x 2,而静态切分允许的最大窗格数目为16 x 16。
  • 51. 8.4文档视图结构切分窗口的CSplitterWnd类操作 CSplitterWnd类封装了窗口切分过程中所需的功能函数,成员函数Create和CreateStatic分别用来创建“动态切分”和“静态切分”的文档窗口,原型: BOOL Create( CWnd* pParentWnd, int nMaxRows, int nMaxCols, SIZE sizeMin, CCreateContext* pContext, DWORD dwStyle = WS_CHILD | WS_VISIBLE |WS_HSCROLL | WS_VSCROLL | SPLS_DYNAMIC_SPLIT, UINT nID = AFX_IDW_PANE_FIRST ); BOOL CreateStatic( CWnd* pParentWnd, int nRows, int nCols, DWORD dwStyle = WS_CHILD | WS_VISIBLE, UINT nID = AFX_IDW_PANE_FIRST ); CSplitterWnd类成员函数CreateView用来为静态窗格指定一个视图类,并创建视图窗口,其函数原型如下: BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext );
  • 52. 8.4文档视图结构静态切分窗口简单示例 [例Ex_SplitSDI] 将单文档应用程序中的文档窗口静态分成3 x 2个窗格。 (1)  用MFC AppWizard创建一个单文档应用程序Ex_SplitSDI。 (2)  打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分窗口的数据成员,如下面的定义: protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; CSplitterWnd m_wndSplitter; (3)用MFC ClassWizard创建一个新的视图类CDemoView(基类为CView)用于与静态切分的窗格相关联。
  • 53. 8.4文档视图结构(4) 为CMainFrame类添加OnCreateClient函数重载,添加代码: BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { CRect rc; GetClientRect(rc); // 获取客户区大小 CSize paneSize(rc.Width()/2-16,rc.Height()/3-16); // 计算每个窗格的平均尺寸 m_wndSplitter.CreateStatic(this,3,2);// 创建3 x 2个静态窗格 m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CDemoView), paneSize,pContext); // 为相应的窗格指定视图类 m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDemoView), paneSize,pContext); m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CDemoView), paneSize,pContext); m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CDemoView), paneSize,pContext); m_wndSplitter.CreateView(2,0,RUNTIME_CLASS(CDemoView), paneSize,pContext); m_wndSplitter.CreateView(2,1,RUNTIME_CLASS(CDemoView), paneSize,pContext); return TRUE; }
  • 54. 8.4文档视图结构(5)  在MainFrm.cpp源文件的开始处,添加视图类CDemoView的包含文件: #include "DemoView.h“ (6)编译并运行,如图。 说明: (1)调用CreateStatic函数创建静态切分窗口后,必须将每个窗格用CreateView函数指定相关联的视图类。各窗格的视图类可相同,也可不同。 (2)切分功能只应用于文档窗口,对于单文档应用程序切分的创建是在CMainFrame类进行的,对于多文档应用程序,添加切分功能时应在文档子窗口类CChildFrame中进行操作。 切分条 第0,0窗格第0,1窗格第1,0窗格第1,1窗格第2,0窗格第2,1窗格
  • 55. 8.4文档视图结构动态切分窗口简单示例 [例Ex_DySplit] 通过添加切分窗口组件来创建动态切分。 (1)   用MFC AppWizard创建一个单文档应用程序Ex_DySplit。 (2)  选择“工程”“添加工程”“Components and Controls”,如图。
  • 56. 8.4文档视图结构 (3)双击“Visual C++ Components”,选中Splitter Bar,如图。 (4)单击[Insert],询问是否要插入Splitter Bar组件,单击[确定],如图。从中可选择切分类型:Horizontal(水平切分)、Vertical(垂直切分)和Both(水平垂直切分)。
  • 57. 8.4文档视图结构(5) 选中Both,单击[OK],回到图8.30对话框,单击[结束],动态切分就被添加到单文档应用程序的主框架窗口类CMainFrame中。 (6)编译运行,如图。 垂直切分块 水平切分块
  • 58. 8.4文档视图结构8.4.4一档多视 一档多视模式 MFC对于“一档多视”提供下列三个模式: (1)    在各自MDI文档窗口中包含同一个视图类的多个视图对象。 有时需要应用程序能为同一个文档打开另一个文档窗口,以便能同时使用两个文档窗口来查看文档的不同部分内容。用MFC AppWizard创建的多文档应用程序支持这种模式,选择“窗口”菜单的“新建窗口”命令时,系统就会为第一个文档窗口创建一个副本。 (2)   在同一个文档窗口中包含同一个视图类的多个视图对象。 这种模式实际上是使用“切分窗口”机制使SDI应用程序具有多视的特征。 (3)    在单独一个文档窗口中包含不同视图类的多个视图对象。 在该模式下,多个视图共享同一个文档窗口。它有点象“切分窗口”,但由于视图可由不同的视图类构造,所以同一个文档可以有不同的显示方法。
  • 59. 8.4文档视图结构示例 [例Ex_Rect] 一档多视示例。 创建表单应用程序,设计表单 (1) 用MFC AppWizard创建一个多文档应用程序Ex_Rect。在第6步中将视图的基类选择为CFormView。 (2)打开表单模板资源IDD_EX_RECT_FORM,参看图,调整表单模板大小。
  • 60. 8.4文档视图结构 (3)打开MFC ClassWizard的Member Variables标签,在Class name中选择CEx_RectView,选中所需的控件ID号,双击鼠标或单击Add Variables。依次为下列控件添加成员变量。
  • 61. 8.4文档视图结构添加CEx_RectDoc和CEx_RectView类代码 (1)  在CEx_RectDoc类中添加一个公有型的CPoint数据成员m_ptRect。 (2)   在CEx_RectDoc类的构造函数处添加下列代码: CEx_RectDoc::CEx_RectDoc() { m_ptRect.x = m_ptRect.y = 0; // 或m_ptRect = CPoint(0,0) } (3) 打开MFC ClassWizard的Messsage Maps标签页,为编辑框IDC_EDIT1和IDC_EDIT2添加EN_CHANGE的消息映射,使它们的映射函数名都设为OnChangeEdit,添加代码: void CEx_RectView::OnChangeEdit() { UpdateData(TRUE); CEx_RectDoc* pDoc = (CEx_RectDoc*)GetDocument(); pDoc->m_ptRect.x = m_CoorX; pDoc->m_ptRect.y = m_CoorY; CPoint pt(m_CoorX, m_CoorY); pDoc->UpdateAllViews(NULL, 2, (CObject *)&pt); }
  • 62. 8.4文档视图结构(4)  用MFC ClassWizard为CEx_RectView添加OnUpdate的消息函数,添加代码: void CEx_RectView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint == 1) { CPoint* pPoint = (CPoint *)pHint; m_CoorX = pPoint->x; m_CoorY = pPoint->y; UpdateData(FALSE); // 在控件中显示 CEx_RectDoc* pDoc = (CEx_RectDoc*)GetDocument(); pDoc->m_ptRect = *pPoint; // 保存在文档类中的m_ptRect } } (5)    在CEx_RectView::OnInitialUpdate中添加一些初始化代码: void CEx_RectView::OnInitialUpdate() { CFormView::OnInitialUpdate(); ResizeParentToFit(); CEx_RectDoc* pDoc = (CEx_RectDoc*)GetDocument(); m_CoorX = pDoc->m_ptRect.x; m_CoorY = pDoc->m_ptRect.y; m_SpinX.SetRange(0, 1024); m_SpinY.SetRange(0, 768); UpdateData(FALSE); }
  • 63. 8.4文档视图结构(6)    这时编译并运行程序,程序会出现一个运行错误。造成这个错误的原因是因为旋转按钮控件在设置范围时,会自动对其伙伴窗口(编辑框控件)进行更新,而此时编辑框控件还没有完全创建好,处理的方法如下面的操作。 (7)   为CEx_RectView添加一个BOOL型的成员变量m_bEditOK。 (8)  在CEx_RectView构造函数中将m_bEditOK的初值设为FALSE。 (9)  在CEx_RectView::OnInitialUpdate函数的最后将m_bEditOK置为TRUE,如下面的代码: void CEx_RectView::OnInitialUpdate() { … UpdateData(FALSE); m_bEditOK = TRUE; } (10)    在CEx_RectView::OnChangeEdit函数的最前面添加下列语句: void CEx_RectView::OnChangeEdit() { if (!m_bEditOK) return; … }
  • 64. 8.4文档视图结构新增CDrawView类,添加框架窗口切分功能 (1)    用MFC ClassWizard为添加一个新的CView的派生类CDrawView。 (2)    为CChildFrame类添加OnCreateClient函数的重载,添加代码: BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { CRect rect; GetWindowRect( &rect );  BOOL bRes = m_wndSplitter.CreateStatic(this, 1, 2); // 创建2个水平静态窗格 m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CEx_RectView), CSize(0,0), pContext); m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDrawView), CSize(0,0), pContext); m_wndSplitter.SetColumnInfo(0, rect.Width()/2, 10); // 设置列宽 m_wndSplitter.SetColumnInfo(1, rect.Width()/2, 10); m_wndSplitter.RecalcLayout(); // 重新布局 return bRes; //CMDIChildWnd::OnCreateClient(lpcs, pContext); }
  • 65. 8.4文档视图结构(3)    在ChildFrm.cpp的前面添加下列语句: #include "ChildFrm.h" #include "Ex_RectView.h" #include "DrawView.h" (4)    打开ChildFrm.h文件,为CChildFrame类添加下列成员变量: public: CSplitterWnd m_wndSplitter; (5)  此时编译,程序会有错误:在用标准C/C++设计程序时,有一个原则即两个代码文件不能相互包含,且多次包含会造成重复定义的错误。为了解决这个难题,使用#pragma once来通知编译器在生成时只包含(打开)一次,因此在用向导创建的所有类的头文件中都有#pragma once这样的语句。正是由于这个语句而造成了在第二次#include后编译器无法正确识别所引用的类。解决的办法是在相互包含时加入类的声明来通知编译器这个类是一个实际的调用,如下一步操作。 (6) 打开Ex_RectView.h,在class CEx_RectView : public CFormView语句前添加下列代码: class CEx_RectDoc; class CEx_RectView : public CFormView { … }
  • 66. 8.4文档视图结构添加CDrawView类代码 (1)  为CDrawView类添加一个公有型的CPoint数据成员m_ptDraw。 (2)    在CDrawView::OnDraw函数中添加下列代码: void CDrawView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); CRect rc(m_ptDraw.x-5, m_ptDraw.y-5, m_ptDraw.x+5, m_ptDraw.y+5); pDC->Rectangle(rc); } (3) 为CDrawView类添加OnInitialUpdate的消息函数,并添加下列代码: void CDrawView::OnInitialUpdate() { CView::OnInitialUpdate(); CEx_RectDoc* pDoc = (CEx_RectDoc*)m_pDocument; m_ptDraw = pDoc->m_ptRect; } (4)  在DrawView.cpp文件的前面添加CEx_RectDoc类的包含语句: #include "Ex_Rect.h" #include "DrawView.h" #include "Ex_RectDoc.h"
  • 67. 8.4文档视图结构(5)  为CDrawView类添加OnUpdate的消息函数,并添加下列代码: void CDrawView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint == 2) { CPoint* pPoint = (CPoint *)pHint; m_ptDraw = *pPoint; Invalidate(); } } (6) 为CDrawView类添加WM_LBUTTONDOWN的消息映射,并添加下列代码: void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) { m_ptDraw = point; GetDocument()->UpdateAllViews(NULL, 1, (CObject*)&m_ptDraw); Invalidate(); // 强迫调用CDrawView::OnDraw CView::OnLButtonDown(nFlags, point); }
  • 68. 8.4文档视图结构(7)    编译运行并测试。 几个视图之间的数据传输是通过CDocument::UpdateAllViews和CView::OnUpdate的相互作用来实现的,为了避免传输的相互干涉,采用提示号(lHint)来区分。 void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) { ... GetDocument()->UpdateAllViews(NULL, 1, (CObject*)&m_ptDraw); // 传送数据 ... } void CEx_RectView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint == 1) // 接收时,通过提示号来判断 { ... } } CEx_RectView类中的编辑框控件数据改变后,经文档类调用UpdateAllViews函数传递,提示号为2,CDrawView类接收数据时,通过OnUpdate函数判断提示号来决定接收数据。 为了能及时更新并保存文档数据,相应的数据成员应在用户文档类中定义。这样,由于所有的视图类都可与文档类进行交互,因而可以共享这些数据。 在为文档创建另一个视图时,该视图的CView::OnInitialUpdate将被调用。