Presentation is loading. Please wait.

Presentation is loading. Please wait.

第6章数据和文档 6.1CString类 6.2使用简单数组集合类 6.3使用CFile类 6.4文档序列化 6.5综合应用.

Similar presentations


Presentation on theme: "第6章数据和文档 6.1CString类 6.2使用简单数组集合类 6.3使用CFile类 6.4文档序列化 6.5综合应用."— Presentation transcript:

1 第6章数据和文档 6.1CString类 6.2使用简单数组集合类 6.3使用CFile类 6.4文档序列化 6.5综合应用

2 (ANSI)|Unicode|Geniric
6.1CString类 BSTR、const char*、LPCTSTR和CString 什么是BSTR、LPSTR以及LPWSTR呢? BSTR(Basic STRing,Basic字符串)是一个OLE CHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下也得到了较为广泛的使用。 LPSTR和LPWSTR是Win32和Visual C++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在Visual C++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图6.1所示。 LP[C][W|T]STR Long pointer to a String Constant (ANSI)|Unicode|Geniric 图6.1 字符串类型表示的含义

3 6.1CString类 例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。 一般地,Visual C++中还有下列类型定义: #ifdef UNICODE typedef LPWSTR LPTSTR; typedef LPCWSTR LPCTSTR; #else typedef LPSTR LPTSTR; typedef LPCSTR LPCTSTR; #endif

4 6.1CString类 CString类支持字符串类型,并可通过CString类构造函数和一些运算符进行构造。CString类构造函数原型如下: CString( ); CString( const CString& stringSrc ); CString( TCHAR ch, int nRepeat = 1 ); CString( LPCTSTR lpch, int nLength ); CString( const unsigned char* psz ); CString( LPCWSTR lpsz ); CString( LPCSTR lpsz ); 例如: CString s1; // 创建一个空字符串 CString s2( "cat" ); // 从C语言样式的字符串来创建s2 CString s3 = s2; // 使用拷贝构造函数,将s2作为s3的初值 CString s4( s2 + " " + s3 ); // 从一个字符串表达式来创建s4 CString s5( 'x' ); // 使s5 = "x" CString s6( 'x', 6 ); // 使s6 = "xxxxxx" CString s7((LPCSTR)ID_FILE_NEW); // 从资源ID_FILE_NEW的字符串值创建s7 // 等同于: // CString s7 ; // s7. LoadString( ID_FILE_NEW ) ; CString city = "Philadelphia"; // 从C语言样式的字符串来创建city

5 6.1CString类 当然,也可使用CString类的Format成员函数将任意数据类型转换成CString字符串。Format成员函数使用C语言的printf的格式样式进行创建,例如: CString str; str.Format( "Floating point: %.2f\n", ); str.Format( "Left-justified integer: %.6d\n", 35); 若将一个CString字符串向上述字符串类型进行转换,则可使用CString类提供了的const char*、LPCTSTR运算符以及AllocSysString和SetSysString成员函数等。例如: // 将CString向LPTSTR转换的方法一 CString theString( "This is a test" ); LPTSTR lpsz = new TCHAR[theString.GetLength()+1]; // TCHAR在Unicode平台中等同于WCHAR(16位Unicode字符),在ANSI中等价于char。 _tcscpy( lpsz, theString); // 将CString向LPTSTR转换的方法二 LPTSTR lpsz = (LPTSTR)(LPCTSTR)theString; // 将CString向BSTR转换 CString str("This is a test"); BSTR bstrText = str.AllocSysString(); SysFreeString(bstrText); // 用完释放

6 6.1CString类 字符串的字符访问 在CString类中,可以用SetAt和GetAt来设置或获取指定字符串中的字符,也可以使用运算符“[ ]”来直接操作。它们的函数原型描述如下: void SetAt( int nIndex, TCHAR ch ); 其中,参数nIndex 用来指CString对象中的某个字符的索引(从零开始),它的值必须大于或等于0,且应小于由GetLength的返回的值。ch用来指定要插入的字符。这样,就可将一个CString对象看作是一个字符数组,SetAt成员函数用来改写指定索引的字符。 TCHAR GetAt( int nIndex ) const; 该函数用来返回由nIndex指定索引位置(从零开始)的TCHAR字符。例如: CString str( "abcdef" ); ASSERT( str.GetAt(2) == 'c' ); // 断言返回的字符与'c'相等。在MFC中,断言机制常用于调试,当断言失败后,程序在此中断, // 然后弹出对话框,询问是否进入调试或选择其他操作 TCHAR operator []( int nIndex ) const; 这是一个运算符重载函数,即将一个CString对象看作是一个字符数组,使用下标运行符“[]”,通过指定下标值nIndex来获取相应的字符。例如: CString str( "abc" ); ASSERT( str[1] == 'b' );

7 6.1CString类 清空及字符串长度 清空CString对象可用Empty函数,判断CString对象是否为空用函数IsEmpty,获取CString对象的字符串长度用函数GetLength,它们的原型如下: void Empty( ); 该函数强迫CString对象为空(字符串长度为0)并释放相应的内存。 BOOL IsEmpty( ) const; 该函数用来判断CString对象是否为空(字符串长度为0),“是”为TRUE,“否”为FALSE。 int GetLength( ) const; 该函数用来获取CString对象的字符串长度(字符个数),这个长度不包括字符串结尾的结束符。例如: CString s( "abcdef" ); ASSERT( s.GetLength() == 6 ); 提取和大小写转换 CString类提供许多用来从一个字符串中提取部分字符串的操作函数,也提供了大小写转换函数。下面分别说明。 CString Left( int nCount ) const; 该函数用来从CString对象中提取最前面的nCount个字符作为要提取的子字符串(简称子串)。如果nCount超过了字符串的长度,则整个字符串都被抽取。 CString Mid( int nFirst ) const; CString Mid( int nFirst, int nCount ) const;

8 6.1CString类 该函数函数用从CString对象中提取一个从nFirst(从零开始的索引)指定的位置开始的nCount个字符的子串。若nCount不指定,则提取的子串是从nFirst开始直到字符串结束。 CString Right( int nCount ) const; 该函数用来从CString对象中提取最后面的nCount个字符作为要提取的子字符串。如果nCount超过了字符串的长度,则整个字符串都被抽取。 void MakeLower( ); 该函数用来将CString对象的所有字符转换成小写字符。 void MakeUpper( ); 该函数用来将CString对象的所有字符转换成大写字符。 void TrimLeft( ); void CString::TrimLeft( TCHAR chTarget ); void CString::TrimLeft( LPCTSTR lpszTargets ); 该函数用来将CString对象最左边的空格、空格和tab字符或chTarget指定的字符或lpszTargets指定的子串删除。 void TrimRight( ); void CString::TrimRight( TCHAR chTarget ); void CString::TrimRight( LPCTSTR lpszTargets );

9 6.1CString类 该函数用来将CString对象最后边的空格、空格和tab字符或chTarget指定的字符或lpszTargets指定的子串删除。 例如: CString strBefore; CString strAfter; strBefore = "Hockey is Best!!!!" ; strAfter = strBefore; strAfter.TrimRight('!' ); // strAfter中的字符串“Hockey is Best!!!!”变成了“Hockey is Best” strBefore ="Hockey is Best?!?!?!?!" ; strAfter.TrimRight("?!?"); // strAfter中的字符串“Hockey is Best?!?!?!?!”变成了“Hockey is Best”

10 6.2使用简单数组集合类 1. 简单数组集合类的构造及元素的添加
1. 简单数组集合类的构造及元素的添加 对简单数组集合类构造的方法都是一样的,均是使用各自的构造函数,它们的原型如下: CByteArray CByteArray( ); CDWordArray CDWordArray( ); CObArray CObArray( ); CPtrArray CPtrArray( ); CStringArray CStringArray( ); CUIntArray CUIntArray( ); CWordArray CWordArray( ); 下面的代码说明了简单数组集合类的两种构造方法: CObArray array; // 使用默认的内存块大小 CObArray* pArray = new CObArray; // 使用堆内存中的默认的内存块大小 为了有效使用内存,在使用简单数组集合类之前最好调用成员函数SetSize 设置此数组的大小,与其对应的函数是GetSize,用来返回数组的大小。它们的原型如下: void SetSize( int nNewSize, int nGrowBy = -1 ); int GetSize( ) const;

11 6.2使用简单数组集合类 其中,参数nNewSize用来指定新的元素的数目(必须大小或等于0)。nGrowBy表示当数组需要扩展时允许可添加的最少元素数目,默认时为自动扩展。 向简单数组集合类添加一个元素,可使用成员函数Add和Append,它们的原型如下: int Add( CObject* newElement ); int Append( const CObArray& src ); 其中,Add函数是向数组的末尾添加一个新元素,且数组自动增1。如果调用的函数SetSize的参数nGrowBy 的值大于1,那么扩展内存将被分配。此函数返回被添加的元素序号,元素序号就是数组下标。参数newElement表示要添加的相应类型的数据元素。而Append函数是向数组的末尾添加由src指定的另一个数组的内容。函数返回加入的第一个元素的序号。

12 6.2使用简单数组集合类 2. 访问简单数组集合类的元素
2. 访问简单数组集合类的元素 在MFC中,一个简单数组集合类元素的访问既可以使用GetAt函数,也可使用“[]”操作符,例如: // CObArray::operator []示例 CObArray array; CAge* pa; // CAge是一个用户类 array.Add( new CAge( 21 ) ); // 添加一个元素 array.Add( new CAge( 40 ) ); // 再添加一个元素 pa = (CAge*)array[0]; // 获取元素0 array[0] = new CAge( 30 ); // 替换元素0; // CObArray::GetAt示例 array.Add( new CAge( 21 ) ); // 元素 0 array.Add( new CAge( 40 ) ); // 元素 1

13 6.2使用简单数组集合类 3. 删除简单数组集合类的元素 删除简单数组集合类中的元素一般需要进行以下几个步骤:
3. 删除简单数组集合类的元素 删除简单数组集合类中的元素一般需要进行以下几个步骤: (1)使用函数GetSize和整数下标值访问简单数组集合类中的元素。 (2)若对象元素是在堆内存中创建的,则使用delete操作符删除每一个对象元素。 (3)调用函数RemoveAll删除简单数组集合类中的所有元素。 例如,下面代码是一个CObArray的删除示例: CObArray array; CAge* pa1; CAge* pa2; array.Add( pa1 = new CAge( 21 ) ); array.Add( pa2 = new CAge( 40 ) ); ASSERT( array.GetSize() == 2 ); for (int i=0;i<array.GetSize();i++) delete array.GetAt(i); array.RemoveAll(); 需要说明的是:函数RemoveAll是删除数组中的所有元素,而函数RemoveAt( int nIndex, int nCount = 1)则表示要删除数组中从序号为nIndex元素开始的,数目为nCount的元素。

14 6.3使用CFile类 6.3.1 文件的打开和关闭 在MFC中,使用CFile打开一个文件通常使用下列两个步骤:
文件的打开和关闭 在MFC中,使用CFile打开一个文件通常使用下列两个步骤: 构造一个不带任何参数的CFile对象; (1)调用成员函数Open并指定文件路径以及文件标志。 (2)CFile类的Open函数原型如下: BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL ); 其中,lpszFileName用来指定一个要打开的文件路径,该路径可以是相对的、绝对的或是一个网络文件名(UNC)。nOpenFlags用来指定文件打开的标志,它的值见表6.1。pError用来表示操作失败产生的CFileException指针,CFileException是一个与文件操作有关的异常处理类。函数Open操作成功时返回TRUE,否则为FALSE。

15 6.3使用CFile类 表6.1 CFile类的文件访问方式 方 式 含 义 CFile::modeCreate
方 式 含 义 CFile::modeCreate 表示创建一个新文件,若该文件已存在,则将文件原有内容清除。 CFile::modeNoTruncate 与CFile::modeCreate组合。若文件已存在,不会将文件原有内容清除。 CFile::modeRead 打开文件只读。 CFile::modeReadWrite 打开文件读与写。 CFile::modeWrite 打开文件只写。 CFile::modeNoInherit 防止子线程继承该文件。 CFile::shareDenyNone 共享文件的读和写,若其他线程用相关方式打开过此文件,则创建失败。 CFile::shareDenyRead 禁止其他线程读此共享文件,若其他线程用相关方式打开过此文件,则创建失败。 CFile::shareDenyWrite 禁止其他线程写此共享文件,若其他线程用相关方式打开过此文件,则创建失败。 CFile::shareExclusive 禁止其他线程读写此共享文件,若其他线程用相关方式打开过此文件,即使是当前线程也会使创建失败。

16 6.3使用CFile类 例如,下面的代码将显示如何用读写方式创建一个新文件:
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 ); } 代码中,若文件创建打开有任何问题,Open函数将在它的最后一个参数中返回CFileException(文件异常类)对象,TRACE宏将显示出文件名和表示失败原因的代码。使用AfxThrowFileException函数将获得更详细的有关错误的报告。 与文件“打开”相反的操作是“关闭”,可以使用Close函数来关闭一个文件对象,若该对象是在堆内存中创建的,还需调用delete来删除它(不是删除物理文件)。

17 6.3使用CFile类 6.3.2 文件的读写和定位 CFile类支持文件的读、写和定位操作。它们相关函数的原型如下:
文件的读写和定位 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表示指针移动方式,它可以是CFile::begin(从文件的开始位置)、CFile::current(从文件的当前位置)或CFile::end(从文件的最后位置,但lOff必须为负值才能在文件中定位,否则将超出文件)等。需要说明的是,文件刚打开时,默认的文件指针位置为0,即文件的开始位置。 另外,函数void SeekToBegin( )和DWORD SeekToEnd( )分别将文件指针移动到文件开始和结尾位置,对于后者还将返回文件的大小。

18 6.3使用CFile类 获取文件的有关信息 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 文件大小的字节数 BYTE m_attribute 文件属性 char m_szFullName[_MAX_PATH] 文件名

19 6.3使用CFile类 需要说明的是,static形式的GetStatus函数将获得指定文件名的文件状态,并将文件名复制至m_szFullName中。该函数仅获取文件状态,并没有真正打开文件,这对于测试一个文件的存在性是非常有用的。例如下面的代码: CFile theFile; char* szFileName = "c:\\test\\myfile.dat"; BOOL bOpenOK; CFileStatus status; if( CFile::GetStatus( szFileName, status ) ) // 该文件已存在,直接打开 { bOpenOK = theFile.Open( szFileName, CFile::modeWrite ); } else // 该文件不存在,需要使用modeWrite方式创建它 bOpenOK = theFile.Open( szFileName, CFile::modeCreate | CFile::modeWrite );

20 6.3使用CFile类 CFile示例 如图6.2所示,单击[浏览]按钮,将弹出文件“打开”对话框,从中选择一个文件时,编辑框上方显示出该文件的路径名、创建时间和文件大小,并在编辑框中显示出该文件的内容。 图6.2 CFile示例运行结果

21 6.3使用CFile类 [例Ex_File] 使用CFile (1)创建一个默认的对话框应用程序Ex_File。
(2)将对话框的标题设为“使用CFile”。删除“TODO: 在这里设置对话控制。”静态文本控件和 [取消]按钮,将[确定]按钮标题改为“退出”。 (3)打开对话框网格,参看图6.2的控件布局,添加静态文件控件IDC_STATIC_TITLE(选中“垂直居中”和“凹陷”选项)、一个编辑框IDC_EDIT1(选中“多行”、“水平滚动”、“垂直滚动”和“自动垂直滚动”选项)和一个“打开”按钮IDC_BUTTON_OPEN。 (4)打开MFC ClassWizard的Member Variables页面,为IDC_STATIC_TITLE控件添加Value类型变量m_strTitle,为IDC_EDIT1控件添加Value类型变量m_strContent。 (5)再次打开MFC ClassWizard,切换到Messsage Maps页面,为CEx_FileDlg类添加按钮IDC_BUTTON_OPEN 的BN_CLICKED消息映射,保留默认的映射函数名,并添加下列代码: (6)编译运行并测试。

22 6.4文档序列化 文档模板和字串资源 1. 文档模板 文档应用程序框架是在程序运行时就开始构造的,在单文档应用程序(设为Ex_SDI)的应用程序类InitInstance函数中,可以看到这样的代码: BOOL CEx_SDIApp::InitInstance() { CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, // 资源ID RUNTIME_CLASS(CEx_SDIDoc), // 文档类 RUNTIME_CLASS(CMainFrame), // 主框架窗口类 RUNTIME_CLASS(CEx_SDIView)); // 视图类 AddDocTemplate(pDocTemplate); return TRUE; }

23 6.4文档序列化 类似的,多文档模板类CMultiDocTemplate的构造函数也有相同的定义。如下面的代码(设为Ex_MDI):
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; }

24 6.4文档序列化 2. 文档模板字串资源 从前面的单文档模板类可以看出,为了能将菜单、加速键、图标等资源加载到应用程序框架中,这些资源的标识符都设为了IDR_MAINFRAME。事实上,在MFC文档序列化流程(后面会讨论到)中,文档标题和通用文件“打开”和“保存”对话框的过滤器中文件类型还必需能够在相关的资源给予指定,这个资源就是文档模板字串资源,它是String Table(字符串)资源列表中的IDR_MAINFRAME项,其内容如下(以单文档应用程序Ex_SDI为例): Ex_SDI\n\nEx_SDI\n\n\nExSDI.Document\nEx_SDI Document 可以看出,IDR_MAINFRAME所标识的字符串被分成了一些以“\n”结尾的子串,这些子串共有7段,每段都有特定的用途,其含义如表6.2所示。

25 6.4文档序列化 表6.2 文档模板字符串的含义 IDR_MAINFRAME的子串 串号 用 途 Ex_SDI\n 应用程序窗口标题 \n
表6.2 文档模板字符串的含义 IDR_MAINFRAME的子串 串号 用 途 Ex_SDI\n 应用程序窗口标题 \n 1 文档根名。对多文档应用程序来说,若在文档窗口标题上显示“Sheet1”,则其中的Sheet就是文档根名。若该子串为空,则文档名为默认的“无标题” 2 新建文档的类型名。若有多个文档类型,则这个名称将出现在“新建”对话框中。 3 通用对话框的文件过滤器正文 4 通用对话框的文件扩展名 ExSDI.Document\n 5 在注册表中登记的文档类型标识 Ex_SDI Document 6 在注册表中登记的文档类型名称

26 6.4文档序列化 但对于MDI来说,上述的字串分别由IDR_MAINFRAME和IDR_EX_MDITYPE(若项目名为Ex_MDI)组成;其中,IDR_MAINFRAME表示窗口标题,而IDR_EX_MDITYPE表示后6项内容。它们的内容如下: IDR_MAINFRAME:Ex_MDI图6.3 Advanced Options对话框 IDR_EX_MDITYPE:\nEx_MDI\nEx_MDI\n\n\nExMDI.Document\nEx_MDI Document 实际上,文档模板字串资源内容既可直接通 过上述字串资源编辑器进行修改,也可以在文档 应用程序创建向导的第四步中,单击[高级]按钮, 通过“高级选项(Advanced Options)”对话框 中的“文档字符模板(Document Template Strings) ”页面来指定,如图6.3所示(以单文档应用程序 Ex_SDI为例)。图中的数字表示该项的含义与 表6.3中对应串号的含义相同。 图6.3 Advanced Options对话框 5 3 6 2 1 4

27 6.4文档序列化 文档序列化过程 MFC文档序列化过程包括:创建空文档、打开文档、保存文档和关闭文档这几个操作,下面来阐述它们的具体运行过程。 1. 创建空文档 应用程序类的InitInstance函数在调用了AddDocTemplate函数之后,会通过CWinApp:: ProcessShellCommand间接调用CWinApp的另一个非常有用的成员函数OnFileNew,并依次完成下列工作: (1)构造文档对象,但并不从磁盘中读数据。 (2)构造主框架类CMainFrame的对象,并创建该主框架窗口,但不显示。 (3)构造视图对象,并创建视图窗口,也不显示。 (4)通过内部机制,使文档、主框架和视图“对象”之间“真正”建立联系。注意与 AddDocTemplate函数的区别,AddDocTemplate函数建立的是“类”之间的联系。 (5)调用文档对象的CDocument::OnNewDocument虚函数,并调用CDocument:: DeleteContents虚函数来清除文档对象的内容。

28 6.4文档序列化 (6)调用视图对象的CView::OnInitialUpdate虚函数对视图进行初始化操作。
(7)调用框架对象的CFrameWnd::ActiveFrame虚函数,以便显示出带有菜单、工具栏、状态栏以及视图窗口的主框架窗口。 在单文档应用程序中,文档、主框架以及视图对象仅被创建一次,并且这些对象在整个运行过程中都有效。CWinApp::OnFileNew函数被InitInstance函数所调用。但当用户选择“文件(File)”菜单中的“新建(New)”时,CWinApp::OnFileNew也会被调用,但与InitInstance不同的是,这种情况下不再创建文档、主框架以及视图对象,但上述过程的最后三个步骤仍然会被执行。 2. 打开文档 当MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“打开(Open)”命令(ID号为ID_FILE_OPEN)映射到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函数还会进一步完成下列工作:

29 6.4文档序列化 (1)弹出通用文件“打开”对话框,供用户选择一个文档。
(2)文档指定后,调用文档对象的CDocument:: OnOpenDocument虚函数。该函数将打开文档,并调用DeleteContents清除文档对象的内容,然后创建一个CArchive对象用于 数据的读取,接着又自动调用Serialize函数。 (3)调用视图对象的CView::OnInitialUpdate虚函数。 除了使用“文件(File)”→“打开(Open)”菜单项外,用户也可以选择最近使用过的文件列表来打开相应的文档。在应用程序的运行过程中,系统会记录下4个默认最近使用过的文件,并将文件名保存在Windows的注册表中。当每次启动应用程序时,应用程序都会最近使用过的文件名称显示在“文件(File)”菜单中。 3. 保存文档 (1)当MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“保存(Save)”命令与文档类CDocument的OnFileSave函数在内部关联起来,但用户在程序框架中看不到相应的代码。OnFileSave函数还会进一步完成下列工作: (2)弹出通用文件“保存”对话框,让用户提供一个文件名。 (3)调用文档对象的CDocument::OnSaveDocument虚函数,接着又自动调用Serialize函数,将CArchive对象的内容保存在文档中。

30 6.4文档序列化 需要说明的是: ● 只有在保存文档之前还没有存过盘(亦即没有文件名)或读取的文档是“只读”的, OnFileSave函数才会弹出通用“保存”对话框。否则,只执行第二步。 ● “文件(File)”菜单中还有一个“另存为(Save As)”命令,它是与文档类CDocument的 OnFileSaveAs函数相关联。不管文档有没有保存过,OnFileSaveAs都会执行上述两个步骤。 ● 上述文档存盘的必要操作都是由系统自动完成的。 4. 关闭文档 当用户试图关闭文档(或退出应用程序)时,应用程序会根据用户对文档的修改与否来进一步完成下列任务: (1)若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保存。当用户选择“是”,则应用程序执行OnFileSave过程。 (2)调用CDocument::OnCloseDocument虚函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类CDocument的DeleteContents清除文档数据。

31 6.4文档序列化 6.4.3 CArchive类和序列化操作
从上述的单文档序列化过程可以看出:打开和保存文档时,系统都会自动调用Serialize函数。事实上,MFC AppWizard在创建文档应用程序框架时已在文档类中重载了Serialize函数,通过在该函数中添加代码可达到实现数据序列化的目的。例如,在Ex_SDI单文档应用程序的文档类中有这样的默认代码: void CEx_SDIDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) // 当文档数据需要存盘时 // TODO: add storing code here } else // 当文档数据需要读取时 { // TODO: add loading code here 代码中,Serialize函数的参数ar是一个CArchive类引用变量。通过判断ar.IsStoring的结果是“真”还是“假”就可决定向文档写或读数据。

32 6.4文档序列化 CArchive(归档)类提供对文件数据进行缓存,它同时还保存一个内部标记,用来标识文档是存入(写盘)还是载入(读盘)。每次只能有一个活动的存档与ar相连。通过CArchive类可以简化文件操作,它提供“<<”和“>>”运算符,用于向文件写入简单的数据类型以及从文件中读取它们,表6.6列出了CArchive所支持的的常用数据类型。 表6.3 ar中可以使用<<和>>运算符的数据类型 类 型 描 述 BYTE 8位无符号整型 WORD 16位无符号整型 LONG 32位带符号整型 DWORD 32位无符号整型 float 单精度浮点 double 双精度浮点 int 带符号整型 short 带符号短整型 char 字符型 unsigned 无符号整型

33 6.4文档序列化 除了“<<”和“>>”运算符外,CArchive类还提供成员函数ReadString和WriteString用来从一个文件对象中读写一行文本,它们的原型如下: Bool ReadString(CString& rString ); LPTSTR ReadString( LPTSTR lpsz, UINT nMax ); void WriteString( LPCTSTR lpsz ); 其中,lpsz用来指定读或写的文本内容,nMax用来指定可以读出的最大字符个数。需要说明的是,当向一个文件写一行字符串时,字符 ‘\0’和‘\n’都不会写到文件中,在使用时要特别注意。 下面举一个简单的示例来说明Serialize函数和CArchive类的文档序列化操作方法。 [例Ex_SDIArchive] 一个简单的文档序列化示例 (1)创建一个默认的单文档应用程序Ex_SDIArchive。 (2)打开String Table资源,将文档模板字串资源IDR_MAINFRAME内容修改为: 文档序列化操作\n\n\n自定义文件(*.my)\n.my\nExSDIArchive.Document\nEx_SDI Document

34 6.4文档序列化 (3)为CEx_SDIArchiveDoc类添加下列成员变量: public:
char m_chArchive[100]; // 读写数据时使用 CString m_strArchive; // 读写数据时使用 BOOL m_bIsMyDoc; // 用于判断文档 (4)在CEx_SDIArchiveDoc类构造函数中添加下列代码: CEx_SDIArchiveDoc::CEx_SDIArchiveDoc() { m_bIsMyDoc = FALSE; } (5)在CEx_SDIArchiveDoc::OnNewDocument函数中添加下列代码: BOOL CEx_SDIArchiveDoc::OnNewDocument() if (!CDocument::OnNewDocument()) return FALSE; strcpy(m_chArchive, "&这是一个用于测试文档的内容!"); m_strArchive = "这是一行文本!"; m_bIsMyDoc = TRUE; return TRUE;

35 6.4文档序列化 (6)在CEx_SDIArchiveDoc::Serialize函数中添加下列代码:
(7)编译运行并测试。程序运行后,选择“文件”→“另存为”菜单,指定一个文档名1.my,然后选择“文件”→“新建”菜单,再打开该文档,结果就会弹出对话框,显示该文档的内容,如图6.4所示。 图6.4 显示文档内容

36 6.4文档序列化 6.4.4 CArchive类和CFile类关联
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表示内部文件的缓冲区大小,默认值为4096字节。lpBuf表示自定义的缓冲区指针,若为NULL,则表示缓冲区建立在堆内存中,当对象清除时,缓冲区内存也被释放;若指明用户缓冲区,对象消除时,缓冲区内存不会被释放。nMode用来指定文档是用于存入还是读取,它可以是CArchive::load(读取数据)、CArchive::store(存入数据)或 CArchive::bNoFlushOnDelete(当析构函数被调用时,避免文档自动调用Flush 。若设置这个标志,则必须在析构函数被调用之前调用Close。否则文件数据将被破坏)。 也可将一个CArchive 对象与CFile类指针相关联,如下面的代码(ar是CArchive 对象): const CFile* fp = ar.GetFile();

37 6.5综合应用 在用文件来存取数据时,最大的难度是要保证读取的数据的正确性。若有一个记录结构,包括学生的姓名(字符串)、学号(字符串)以及三门课程成绩,则如何保证在文件读写的正确性呢?一种办法是将记录定义成C结构体类型,使用CFile来操作,这是避开MFC文档序列化机制而进行的方法,虽有效但缺少对MFC机制和类的应用,因为在C++中,C结构体被看作是类的一种简单形式。另一种方法,是将记录声明成一个类,并使该类具体可序列化特性。一个可序列化的类的对象可以在Serialize函数使用CArchive对象通过“<<”和“>>”来正确地向文件进行写入和读取操作。 图6.5 Ex_Student运行结果

38 6.5综合应用 下面来看一个综合应用,如图6.5所示。它首先通过对话框来输入一个学生记录,记录包括学生的姓名、学号和三门成绩,用类CStudent来描述,并使其可序列化。然后将记录内容保存到一个对象数组集合类对象中,最后通过文档序列化将记录保存到一个文件中。当添加记录或打开一个记录文件,还会将数据显示在文档窗口(即视图)中。 [例Ex_Student] 文档序列化示例 1)添加用于学生记录输入的对话框 (1)创建一个默认的单文档应用程序Ex_Student。 (2)向应用程序中添加一个对话框资源,打开属性对话框将其字体设置为“宋体,9”,标题改为“添加学生记录”,取默认的ID号IDD_DIALOG1,将OK和Cancel按钮的标题分别改为“确 定”和“取 消”。 (3)参看图6.5的控件布局,用编辑器为对话框添加如下表6.4所示的一些控件。

39 6.5综合应用 表6.4 添加的控件 控 件 ID号 标 题 属 性 静态文本 默认 姓名: 学号: 成绩1: 成绩2: 成绩3: 编辑框
表6.4 添加的控件 控 件 ID号 标 题 属 性 静态文本 默认 姓名: 学号: 成绩1: 成绩2: 成绩3: 编辑框 IDC_EDIT1 —— IDC_EDIT2 IDC_EDIT3 IDC_EDIT4 IDC_EDIT5 静态图片 Frame, Etched, 其余默认

40 6.5综合应用 (4)双击对话框模板或按Ctrl+W快捷键,为对话框资源IDD_DIALOG1创建一个对话框类CInputDlg。
(5)打开ClassWizard的Member Variables标签,在Class name中选择CInputDlg,选中所需的控件ID标识符,双击鼠标或单击Add Variables按钮。依次为表6.5控件增加成员变量。 表6.5 控件变量 控件ID号 变量类型 变量名 范围和大小 IDC_EDIT1 CString m_strName 20 IDC_EDIT2 m_strID IDC_EDIT3 float m_fScore1 0~100 IDC_EDIT4 m_fScore2 IDC_EDIT5 m_fScore3

41 6.5综合应用 2)添加一个CStudent类并使该类可序列化
一个可序列化的类必须是CObject的一个派生类,且在类声明中,需要包含DECLARE_SERIAL宏调用,而在类的实现文件中包含IMPLEMENT_SERIAL宏调用,这个宏有3个参数:前2个参数分别表示类名和基类名,第3个参数表示应用程序的版本号。最后还需要重载Serialize函数,使该类的数据成员进行相关序列化操作。 由于使用ClassWizard无法添加一个CObject派生类,因此必须手动进行。为了简化类文件的复杂性,这里创建的这个CStudent类的声明和实现代码是直接添加在Ex_StudentDoc.h和Ex_StudentDoc.cpp文件中的,具体如下: 3)添加并处理菜单项 (1)在菜单资源的主菜单中增加顶层菜单项“学生记录(&S)”,在该顶层菜单项中增加子菜单“添加(&A)”(ID_STUREC_ADD)。 (2)用ClassWizard为CEx_StudentDoc类添加ID_STUREC_ADD的COMMAND消息映射,并在映射函数中添加下列代码:

42 6.5综合应用 void CEx_StudentDoc::OnSturecAdd()
{ CInputDlg dlg; if (IDOK == dlg.DoModal()) // 添加记录 CStudent *pStudent = new CStudent(dlg.m_strName, dlg.m_strID, dlg.m_fScore1, dlg.m_fScore2, dlg.m_fScore3); m_stuObArray.Add(pStudent); SetModifiedFlag(); // 设置文档更改标志 UpdateAllViews(NULL); // 更新视图 } (3)在Ex_StudentDoc.cpp文件的开始处,增加包含CAddDlg的头文件。 #include "Ex_StudentDoc.h" #include "InputDlg.h"

43 6.5综合应用 3)完善代码 (1)在Ex_StudentDoc.h文件中,为CEx_StudentDoc类添加下列成员变量和成员函数:
public: CObArray m_stuObArray; int GetAllRecNum(void); CStudent * GetStudentAt(int nIndex); (2)在Ex_StudentDoc.cpp文件中,添加函数的实现代码: CStudent * CEx_StudentDoc::GetStudentAt(int nIndex) { if ((nIndex < 0) || nIndex > m_stuObArray.GetUpperBound()) return 0; // 超界处理 return (CStudent *)m_stuObArray.GetAt(nIndex); } int CEx_StudentDoc::GetAllRecNum() return m_stuObArray.GetSize();

44 6.5综合应用 (3)在CEx_StudentDoc析构函数中添加下列代码:
CEx_StudentDoc::~CEx_StudentDoc() { int nIndex = GetAllRecNum(); while (nIndex--) delete m_stuObArray.GetAt(nIndex); m_stuObArray.RemoveAll(); } (4)在Serialize函数中添加下列代码: void CEx_StudentDoc::Serialize(CArchive& ar) if (ar.IsStoring()) m_stuObArray.Serialize(ar); } else

45 6.5综合应用 需要说明的是,m_stuObArray是一个对象数组集合类CObArray的对象,当读取数据时调用Serialize成员函数时,它实际上是调用集合类对象中的元素的Serialize成员函数,并将对象添加到m_stuObArray中。那么它又是怎么知道元素是调用CStudent类的Serialize成员函数呢?这是因为当添加学生成绩记录后,一旦保存到文件中,就会将CStudent类名同时存在到文件中,当读取时,就会自动使用CStudent类。这是CObArray序列化的一个内部机制。 (5)在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; } (6)打开文档的字串资源IDR_MAINFRAME,将其内容修改为: Ex_Student\nStudentRec\nEx_Stu\n记录文件 (*.rec)\n.rec\nExStudent.Document\nEx _Stu Document (7)编译运行并测试,结果如前图6.5所示。

46 习题 1. 试举例说明MFC简单数组集合类的查找和修改方法。 2. 文档字串资源有哪些含义?如何编辑字串资源? 3. 若想通过对文档字串资源的更改,使应用程序的“打开”或“保存”对话框中的文件类型显示为“C源文件(*.c,*.cpp)”,则应如何实现? 4. 什么是文档的序列化?其过程是怎样的? 5. 若有一个学生记录,包括学生的姓名(字符串)、学号(字符串)和三门成绩。若将它的记录数据转换成CString字符串,则如何转换? 6. 若有非空的CString字符串,能不能在文档类中的Serialize函数里使用CArchive& ar对象的“<<”和“>>”来将字符串写入文档或从文档读取?为什么? 7. 上机练习最后的综合应用实例Ex_Student。


Download ppt "第6章数据和文档 6.1CString类 6.2使用简单数组集合类 6.3使用CFile类 6.4文档序列化 6.5综合应用."

Similar presentations


Ads by Google