第13单元文档读写与打印 256 第13单元文档读写与打印 本单元教学目标 介绍在文档/视图结构中文档读写的基本手段——序列化,以及文档打印的编程技术。 学习要求 理解序列化的基本思想和MFC的打印工作流程,可编写相应的处理程序 授课内容 131序列化( Serialize) 文档对象的序列化( Serialize)是指文档对象可以将其当前状态(由其成员变量的值表 示)写入到永久性存储体(通常是指磁盘)中,以后还可从永久性存储体中读取对象的状态 (载入),从而重建对象。这种对象的保存和恢复的过程称为序列化。保存和载入序列化的 数据通过 CArchive类的对象作为中介来完成。 文档的序列化在文档类的 Serialize()成员函数中进行。当用户选择文件菜单的 file save Save As或Open选项时,都会自动调用这一成员函数。由于应用程序的数据结构各不相同, 所以应重载文档派生类的 Serialize()成员函数,使其支持对特定数据的序列化 App Wizard在生成应用程序时只给出了一个 Serialize()函数的框架,程序员要做的工 作是为其添加代码,以实现具体数据的序列化。 AppWizard生成的 Serialize()函数由一个 简单的 if-else语句组成: oid CMy Doc:: Serialze(Carchive& ar if(ar. IsStoringo 7/ TODO: add storing code here. // TODO: add loading code here
第 13 单元 文档读写与打印 - 256 - 第 13 单元 文档读写与打印 本单元教学目标 介绍在文档/视图结构中文档读写的基本手段——序列化,以及文档打印的编程技术。 学习要求 理解序列化的基本思想和 MFC 的打印工作流程,可编写相应的处理程序。 授课内容 13.1 序列化(Serialize) 文档对象的序列化(Serialize)是指文档对象可以将其当前状态(由其成员变量的值表 示)写入到永久性存储体(通常是指磁盘)中,以后还可从永久性存储体中读取对象的状态 (载入),从而重建对象。这种对象的保存和恢复的过程称为序列化。保存和载入序列化的 数据通过 CArchive 类的对象作为中介来完成。 文档的序列化在文档类的Serialize()成员函数中进行。当用户选择文件菜单的File Save、 Save As 或 Open 选项时,都会自动调用这一成员函数。由于应用程序的数据结构各不相同, 所以应重载文档派生类的 Serialize()成员函数,使其支持对特定数据的序列化。 AppWizard 在生成应用程序时只给出了一个 Serialize()函数的框架,程序员要做的工 作是为其添加代码,以实现具体数据的序列化。AppWizard 生成的 Serialize()函数由一个 简单的 if-else 语句组成: void CMyDoc::Serialze(CArchive& ar) { if(ar.IsStoring()) { // TODO: add storing code here. } else { // TODO: add loading code here
第13单元文档读写与打印 其中参数ar是一个 CArchive类型的对象,该对象包含一个CFle类型的文件指针 CArchive 对象为读写 CFile(文件类)对象中的可序列化数据提供了一种类型安全的缓冲机制。通常 CFile类对象代表一个磁盘文件 CArchive类的成员函数 StOring()用于通知 Serialize()函数是需要写入还是读取序 列化数据。如果数据要写入(Save或 Save as), StOring()返回布尔值TRUE;如果数据 是被读取(Open),则返回 FALSE CArchive类对象使用重载的插入(>)操作符执行读和写操作。这种方 式与cin和cou中的输入输出流非常相似,只是这里处理的是对象,不象cin和cout那样, 处理的是ASCI字符串 例13-1序列化。如果例12-1的吹泡泡程序使用一般的数组存放泡泡数据(参看例9-1 的程序) CRect m rectBubble MAX BUBBLE] 为其文档类重新设计 Serialize()函数。 说明:按例12-1的方法建立项目和输入源代码,但将文档类中的泡泡数据改为以 上两行的形式。修改文档类的 Serialize()函数,代码如下 程序 //序列化函数 void CMy Doc:: Serialze(CArchive& ar) if(ar. IsStoringo ar >m rect Bubble[il 分析:在编写序列化函数时,一定要注意写入顺序要和读出顺序一一对应。在本例 中,先写入数据成员 m nBubblecount,再写入泡泡数组 m rect Bubble,那么在读出时也要
第 13 单元 文档读写与打印 - 257 - } } 其中参数 ar 是一个 CArchive 类型的对象,该对象包含一个 CFile 类型的文件指针。CArchive 对象为读写 CFile(文件类)对象中的可序列化数据提供了一种类型安全的缓冲机制。通常 CFile 类对象代表一个磁盘文件。 CArchive 类的成员函数 IsStoring()用于通知 Serialize()函数是需要写入还是读取序 列化数据。如果数据要写入(Save 或 Save As),IsStoring()返回布尔值 TRUE;如果数据 是被读取(Open),则返回 FALSE。 CArchive 类对象使用重载的插入(>)操作符执行读和写操作。这种方 式与 cin 和 cout 中的输入输出流非常相似,只是这里处理的是对象,不象 cin 和 cout 那样, 处理的是 ASCII 字符串。 [例 13-1] 序列化。如果例 12-1 的吹泡泡程序使用一般的数组存放泡泡数据(参看例 9-1 的程序): CRect m_rectBubble[MAX_BUBBLE]; int m_nBubbleCount; 为其文档类重新设计 Serialize()函数。 说 明:按例 12-1 的方法建立项目和输入源代码,但将文档类中的泡泡数据改为以 上两行的形式。修改文档类的 Serialize()函数,代码如下。 程 序: // 序列化函数 void CMyDoc::Serialze(CArchive& ar) { if(ar.IsStoring()) { ar > m_nBubbleCount; for(int i=0; i> m_rectBubble[i]; } } 分 析:在编写序列化函数时,一定要注意写入顺序要和读出顺序一一对应。在本例 中,先写入数据成员 m_nBubbleCount,再写入泡泡数组 m_rectBubble,那么在读出时也要
第13单元文档读写与打印 遵循相同的顺序,先读 m nUbble count的值,然后再读泡泡数组的各元素值 在设计 Serialize()函数时,还要注意各数据之间的关系。在本例中,一定要先读 m nBubbleCount的值,否则在读泡泡数组时还不知道数组中有几个元素,也就无法确定循 环次数。读数据的顺序确定以后,写数据的顺序自然也就定下来了。 因为 CObject类支持序列化( CObject类有 Serialize()成员函数),所以 COjbect派生 类也支持序列化。例如,数组类 CArray支持序列化,所以如果文档数据存放在 CArray类对 象中,就可象例12-1中那样,在重载的文档派生类的 Serialize()成员函数中直接调用 CArray 对象的 Serialize()成员函数: void CMy Doc:: Serialize(CArchive &ar) m rectBubble Serialize(ar) 这正是例12-1中的做法。当然,大多数情况下文档类的数据结构比较复杂,不一定都能处 理得如此简单。 序列化简化了对象的保存和载入编程。但是,序列化本身还是有一定的局限性的。序列 化一次从文件中载入文档中的所有数据,这并不适合于大文件编辑和数据库应用。对于这类 应用,应用程序每次只是从文件中读入一部分数据进行处理。此时,就要避开文档的序列化 机制直接读取和保存文件,例如直接使用第8单元介绍的CFie类 132打印和打印预览 在第12单元中已经介绍过,文档/视图结构中的视图类负责程序的输出,包括屏幕显示 和打印。也就是说,视图类的 On Draw()函数的输出为显示和打印共用。这种安排大大简 化了编程,特别是在使用 App Wizard生成应用程序框架的情况下,几乎无需添加任何编码 就可实现“所见即所得”式的打印输出功能 然而,由于打印机和显示屏上的窗口的工资原理完全不同,各种参数之间存在很大差异, 在 Ondraw()函数中兼顾两者的要求还是有困难的。如果在设计 OnDraw()函数时主要 考虑显示的需要(前面的文档/视图例题程序均如此),则打印输出的质量不高。这是因为 1.打印机和窗口(屏幕)显示的分辨率不同。打印机的分辨率用每英寸多少个点来描 述,屏幕分辨率用单位面积的像素点来表示。同样是Aria字体的字符,在屏幕上用20个 像素表示,而在打印机上则需要50点。因此,如果选用 MM TEXT模式编程(为简单起见 前几单元的示例程序均如此),一个逻辑单位对应于一个像素点,则与屏幕显示相比,打印 尺度明显偏小。 2.窗口和打印机对边界的处理不同。窗口可以看作是无边界的,可以在窗口之外绘图 而不会引起错误,窗口会自动剪裁超出边界的图形。但打印机却不同,打印机按页打印,输 出时必须自己处理分页和换页 例13-2修改例12-1的程序并观察其打印结果
第 13 单元 文档读写与打印 - 258 - 遵循相同的顺序,先读 m_nBubbleCount 的值,然后再读泡泡数组的各元素值。 在设计 Serialize()函数时,还要注意各数据之间的关系。在本例中,一定要先读 m_nBubbleCount 的值,否则在读泡泡数组时还不知道数组中有几个元素,也就无法确定循 环次数。读数据的顺序确定以后,写数据的顺序自然也就定下来了。 因为 CObject 类支持序列化(CObject 类有 Serialize()成员函数),所以 COjbect 派生 类也支持序列化。例如,数组类 CArray 支持序列化,所以如果文档数据存放在 CArray 类对 象中,就可象例 12-1 中那样,在重载的文档派生类的 Serialize()成员函数中直接调用 CArray 对象的 Serialize()成员函数: void CMyDoc::Serialize(CArchive &ar) { m_rectBubble.Serialize(ar); } 这正是例 12-1 中的做法。当然,大多数情况下文档类的数据结构比较复杂,不一定都能处 理得如此简单。 序列化简化了对象的保存和载入编程。但是,序列化本身还是有一定的局限性的。序列 化一次从文件中载入文档中的所有数据,这并不适合于大文件编辑和数据库应用。对于这类 应用,应用程序每次只是从文件中读入一部分数据进行处理。此时,就要避开文档的序列化 机制直接读取和保存文件,例如直接使用第 8 单元介绍的 CFile 类。 13.2 打印和打印预览 在第 12 单元中已经介绍过,文档/视图结构中的视图类负责程序的输出,包括屏幕显示 和打印。也就是说,视图类的 OnDraw()函数的输出为显示和打印共用。这种安排大大简 化了编程,特别是在使用 AppWizard 生成应用程序框架的情况下,几乎无需添加任何编码 就可实现“所见即所得”式的打印输出功能。 然而,由于打印机和显示屏上的窗口的工资原理完全不同,各种参数之间存在很大差异, 在 OnDraw()函数中兼顾两者的要求还是有困难的。如果在设计 OnDraw()函数时主要 考虑显示的需要(前面的文档/视图例题程序均如此),则打印输出的质量不高。这是因为: 1.打印机和窗口(屏幕)显示的分辨率不同。打印机的分辨率用每英寸多少个点来描 述,屏幕分辨率用单位面积的像素点来表示。同样是 Arial 字体的字符,在屏幕上用 20 个 像素表示,而在打印机上则需要 50 点。因此,如果选用 MM_TEXT 模式编程(为简单起见, 前几单元的示例程序均如此),一个逻辑单位对应于一个像素点,则与屏幕显示相比,打印 尺度明显偏小。 2.窗口和打印机对边界的处理不同。窗口可以看作是无边界的,可以在窗口之外绘图 而不会引起错误,窗口会自动剪裁超出边界的图形。但打印机却不同,打印机按页打印,输 出时必须自己处理分页和换页。 [例 13-2] 修改例 12-1 的程序并观察其打印结果
第13单元文档读写与打印 259 程序:在例12-程序的视图类 CMy View类的成员函数 OnDraw()中,添加代码 沿窗口客户区轮廓画一矩形: void CMyView:: OnDraw (CDC* pDC) CRect rect GetClientRect(&rect) pDC->Rectangle(rect) CMy Doc GetDocument o //取文档指针 ASSERT VALID(pI pDC-> SelectstockOb ject( LTGRAY BRUSH);//在视图上显示文档数据 for(int i=0: iG etlistsize pDC->Ellipse(pDoc->GetBubble(i)) 输入输出:用鼠标左键在窗口客户区吹泡泡。打开文件菜单的打印预览选项,可观察打 口无 图13-1缺省映射模式( MM TEXT)下的打印效果 印效果,如图13-1。 分析:通过打印预览,可观察到打印输出集中在打印纸的左上角,窗口客户区矩形 仅占打印纸的很小一部分。在窗口边沿生成的泡泡,在打印时并不受窗口边界的限制。 要正确打印输出屏幕上的内容,就必须解决这些问题。对于第一个问题,解决方法为利 用CDC: SetMap Mode( int nOde)设置其他映射模式,例如采用 MM LOMETRIC模式 该模式的基本单位不是像素,而是0.1毫米。采用这类映射模式编程,可使窗口显示图象和 打印图象的比例相近 但采用非 MM TEXT模式编程相当麻烦。首先,这些逻辑坐标的y轴方向与 MM TEXT 模式不同,下负上正,原点在窗口左上角,所以客户区的y坐标均为负值。第二,由于逻辑 坐标和物理坐标不一致,所以在响应鼠标消息时要进行换算(可参看第10单元的有关内容)。 例13-3改进吹泡泡程序,使之打印输出与屏幕显示的比例相近 程序:在例12-1基础上修改。首先在 CMy View类中重载虚函数 OnPrepareDC()。 在 CMy View类的声明中增加一行:
第 13 单元 文档读写与打印 - 259 - 程 序:在例 12-1 程序的视图类 CMyView 类的成员函数 OnDraw()中,添加代码 沿窗口客户区轮廓画一矩形: void CMyView::OnDraw(CDC* pDC) { CRect rect; GetClientRect(&rect); pDC->Rectangle(rect); CMyDoc* pDoc = GetDocument(); // 取文档指针 ASSERT_VALID(pDoc); pDC->SelectStockObject(LTGRAY_BRUSH); // 在视图上显示文档数据 for(int i=0; iGetListSize(); i++) pDC->Ellipse(pDoc->GetBubble(i)); } 输入输出:用鼠标左键在窗口客户区吹泡泡。打开文件菜单的打印预览选项,可观察打 印效果,如图 13-1。 分 析:通过打印预览,可观察到打印输出集中在打印纸的左上角,窗口客户区矩形 仅占打印纸的很小一部分。在窗口边沿生成的泡泡,在打印时并不受窗口边界的限制。 要正确打印输出屏幕上的内容,就必须解决这些问题。对于第一个问题,解决方法为利 用 CDC::SetMapMode(int nMode)设置其他映射模式,例如采用 MM_LOMETRIC 模式。 该模式的基本单位不是像素,而是 0.1 毫米。采用这类映射模式编程,可使窗口显示图象和 打印图象的比例相近。 但采用非 MM_TEXT 模式编程相当麻烦。首先,这些逻辑坐标的 y 轴方向与 MM_TEXT 模式不同,下负上正,原点在窗口左上角,所以客户区的 y 坐标均为负值。第二,由于逻辑 坐标和物理坐标不一致,所以在响应鼠标消息时要进行换算(可参看第 10 单元的有关内容)。 [例 13-3] 改进吹泡泡程序,使之打印输出与屏幕显示的比例相近。 程 序:在例 12-1 基础上修改。首先在 CMyView 类中重载虚函数 OnPrepareDC()。 在 CMyView 类的声明中增加一行: 图 13-1 缺省映射模式(MM_TEXT)下的打印效果
第13单元文档读写与打印 virtual void OnPrepareDC(CDC pDC, CPrint Info * Info=NULL) 然后添加该函数的定义,设置映射模式为 MM LOMETRIO: //设置映射模式 void CMyView: OnPrepareDC(CDC *pDC, CPrintInfo *pInfo pDC->SetMapMode( MM LOMETRIC CView:: OnPrepareDC (pDC, pInfo) 然后修改消息映射函数 OnLButton Down(),将物理坐标转换为逻辑坐标: /响应点击鼠标左键消息 void CMyView:: OnLBut ton Down ( UINT nFlags, CPoint point 取文档指针 ASSERT VALID (pDoc) CClientDC dc(this) /设置设备环境 OnPrepareDC(&dc) int r= rand(%50+5 //生成泡泡 CRect rect(point x-r, point. y-r, point x+r, point. y+r) InvalidateRect(rect, FALSE);//更新视图 dc. DPtoLP(rect //转换物理坐标为逻辑坐标 pDoc->AddBubble(rect /修改文档数据 Doc->Set ModifiedFlag( //设置修改标志 输入输出:用鼠标左键在窗口客户 无-泡 区吹泡泡。使用文件菜单中的打印选项 可打印窗口图象,图象位于打印纸上部 比例恰当,如图13-2所示 oo○.° 分析:由于 OnDraw()函数 输出使用逻辑坐标,所以存储数据(泡 泡的包含矩形)也使用逻辑坐标。在 OnLButton Down()函数中鼠标位置参 数 point为物理坐标,首先据此生成泡泡 的包含矩形(物理坐标),更新窗口客户 区的相关区域(物理坐标),然后将物理图13-2使用映射模式 MM LOMETRIC的打印效果 坐标的泡泡包含矩形转换为逻辑坐标并 存入文档。 CView类的虚函数 OnPrepareDO()用于设置设备环境,其原型为:
第 13 单元 文档读写与打印 - 260 - virtual void OnPrepareDC(CDC *pDC, CPrintInfo *pInfo=NULL); 然后添加该函数的定义,设置映射模式为 MM_LOMETRIC: // 设置映射模式 void CMyView::OnPrepareDC(CDC *pDC, CPrintInfo *pInfo) { pDC->SetMapMode(MM_LOMETRIC); CView::OnPrepareDC(pDC, pInfo); } 然后修改消息映射函数 OnLButtonDown(),将物理坐标转换为逻辑坐标: // 响应点击鼠标左键消息 void CMyView::OnLButtonDown(UINT nFlags, CPoint point) { CMyDoc* pDoc = GetDocument(); // 取文档指针 ASSERT_VALID(pDoc); CClientDC dc(this); // 设置设备环境 OnPrepareDC(&dc); int r = rand()%50+5; // 生成泡泡 CRect rect(point.x-r, point.y-r, point.x+r, point.y+r); InvalidateRect(rect, FALSE); // 更新视图 dc.DPtoLP(rect); // 转换物理坐标为逻辑坐标 pDoc->AddBubble(rect); // 修改文档数据 pDoc->SetModifiedFlag(); // 设置修改标志 } 输入输出:用鼠标左键在窗口客户 区吹泡泡。使用文件菜单中的打印选项 可打印窗口图象,图象位于打印纸上部, 比例恰当,如图 13-2 所示。 分 析:由于 OnDraw()函数 输出使用逻辑坐标,所以存储数据(泡 泡的包含矩形)也使用逻辑坐标。在 OnLButtonDown()函数中鼠标位置参 数 point 为物理坐标,首先据此生成泡泡 的包含矩形(物理坐标),更新窗口客户 区的相关区域(物理坐标),然后将物理 坐标的泡泡包含矩形转换为逻辑坐标并 存入文档。 CView 类的虚函数 OnPrepareDC()用于设置设备环境,其原型为: 图 13-2 使用映射模式 MM_LOMETRIC 的打印效果
第13单元文档读写与打印 virtual void On PrepareDC( CDC* pDC, CPrintInfo" pInfo= NULL 其中参数pDC为指向设备环境的指针, pInfo为指向 CPrintInfo类对象的指针。 CPrintInfo 类用来存放与打印有关的信息,其数据成员 m n Page为当前打印页的号码; m rectPage 存放着当前打印纸上的可打印区域。常用成员函数有 1.设置从第几页开始打印。其原型为 void SetMinPage( UINT nMinPage ) 其中参数 nMinPage为开始打印的页号。如果从文档的第1页开始打印,则 n MinTage的值 应为 2.设置打印到第几页结束。其原型为: yoid Set MaxPage( UINT nMaxPage 其中参数 n Max Page为最后一个打印页的页码,其缺省值为1 3.取关于打印页码的设置。原型为 UINT GetMin Page( )const UINT Get MaxPage()const 如果 On Draw()主要用于显示,打印内容简单(例如只有一页),则 On PrepareD() 的参数 pInfo可取空值NULL。 应用程序框架在调用 On Draw()之前会调用 On PrepareD()函数。在 On Draw() 之外使用设备环境时(如在消息响应函数中),应首先声明一个 CClientDC对象,然后调用 OnPrepareDC()函数 自学内容 133自定义类的序列化 前面已经介绍过,如果文档类的数据是 CObject的派生类的对象,则文档类的序列化成 员函数 Serialize()的编写非常简单。那么,对于程序中的自定义类,能否让其支持序列化 呢?回答是肯定的 要让程序员自定义的类支持序列化,一般要做如下6步工作: 1.从 CObject类派生出自定义类; 2.重载自定义类的 Serialize()成员函数,加入必要的代码,用以保存自定义类对象 的数据成员到 CArchive对象以及从 CArchive对象载入自定义类对象的数据成员状态 3.在自定义类的声明中,加入 DECLARE SERIAL()宏,这是序列化对象所必需的 4.为自定义类定义一个不带参数的构造函数 5.为自定义类重载赋值运算符“=” 6.在自定义类的源代码文件中加入 IMPLEMENT SERIAL()宏
第 13 单元 文档读写与打印 - 261 - virtual void OnPrepareDC( CDC* pDC, CPrintInfo* pInfo = NULL ); 其中参数 pDC 为指向设备环境的指针,pInfo 为指向 CPrintInfo 类对象的指针。CPrintInfo 类用来存放与打印有关的信息,其数据成员 m_nCurPage 为当前打印页的号码;m_rectPage 存放着当前打印纸上的可打印区域。常用成员函数有: 1. 设置从第几页开始打印。其原型为: void SetMinPage( UINT nMinPage ); 其中参数 nMinPage 为开始打印的页号。如果从文档的第 1 页开始打印,则 nMinPage 的值 应为 1。 2.设置打印到第几页结束。其原型为: void SetMaxPage( UINT nMaxPage ); 其中参数 nMaxPage 为最后一个打印页的页码,其缺省值为 1。 3.取关于打印页码的设置。原型为 UINT GetMinPage( ) const; UINT GetMaxPage( ) const; 如果 OnDraw()主要用于显示,打印内容简单(例如只有一页),则 OnPrepareDC() 的参数 pInfo 可取空值 NULL。 应用程序框架在调用 OnDraw()之前会调用 OnPrepareDC()函数。在 OnDraw() 之外使用设备环境时(如在消息响应函数中),应首先声明一个 CClientDC 对象,然后调用 OnPrepareDC()函数。 自学内容 13.3 自定义类的序列化 前面已经介绍过,如果文档类的数据是 CObject 的派生类的对象,则文档类的序列化成 员函数 Serialize()的编写非常简单。那么,对于程序中的自定义类,能否让其支持序列化 呢?回答是肯定的。 要让程序员自定义的类支持序列化,一般要做如下 6 步工作: 1.从 CObject 类派生出自定义类; 2.重载自定义类的 Serialize()成员函数,加入必要的代码,用以保存自定义类对象 的数据成员到 CArchive 对象以及从 CArchive 对象载入自定义类对象的数据成员状态; 3.在自定义类的声明中,加入 DECLARE_SERIAL()宏,这是序列化对象所必需的; 4.为自定义类定义一个不带参数的构造函数; 5.为自定义类重载赋值运算符“=”; 6.在自定义类的源代码文件中加入 IMPLEMENT_SERIAL()宏
第13单元文档读写与打印 下面以一个自定义的 Person类说明自定义类的序列化过程 例13-41声明一个 Person类,并使之支持序列化 程序: class CPerson: public Cob ject DECLARE SERIAL( CPerson) /身份证号码 /姓名 CString m strAtion;∥/民族 int //性别 nt //年龄 BOOL arri ed;//婚否 public CPerson& operator =(CPerson& person) void Serialize( CArchive& ar) IMPLEMENT SERIAL( CPerson, CObject, 1 CPerson& CPerson:: operator =(CPerson& person m INumber erson. m INumber m strName person m strName m strAtion =person. m strAtion person. m nAge m mArried erson. m mArried return *this } void CPerson: Serialize( CArchive& ar) COb ject: Serialize(ar);//首先调用基类的 Serialize o方法 if(ar. IsStoring o) ar < m strN ar < m strAtion ar < m sEx
第 13 单元 文档读写与打印 - 262 - 下面以一个自定义的 Person 类说明自定义类的序列化过程。 [例 13-4] 声明一个 Person 类,并使之支持序列化。 程 序: class CPerson: public CObject { DECLARE_SERIAL( CPerson) LONG m_IDnumber; // 身份证号码 CString m_strName; // 姓名 CString m_strNation; // 民族 int m_nSex; // 性别 int m_nAge; // 年龄 BOOL m_bMarried; // 婚否 public: CEmployee(){}; CPerson& operator = (CPerson& person); void Serialize(CArchive& ar); }; IMPLEMENT_SERIAL( CPerson, CObject, 1 ) CPerson& CPerson::operator = (CPerson& person) { m_IDnumber = person.m_IDnumber; m_strName = person.m_strName; m_strNation = person.m_strNation; m_nSex = person.m_nSex; m_nAge = person.m_nAge; m_bMarried = person.m_bMarried; return *this; } void CPerson::Serialize(CArchive& ar) { CObject::Serialize( ar); // 首先调用基类的 Serialize()方法 if(ar.IsStoring()) { ar << m_IDnumber; ar << m_strName; ar << m_strNation; ar << m_nSex; ar << m_nAge;
第13单元文档读写与打印 263 ar 〉 m Idnumb ar>〉 m strName ar >>m strAtion: ar >> m nSe ar >>m nAge ar >>(int)m mArried; 分析:MFC在从磁盘文件载入对象状态并重建对象时,需要有一个缺省的不带任 何参数的构造函数以及一个重载的赋值运算符。序列化对象将用该构造函数生成一个对象, 然后调用 Serialize()函数,用重建对象所需的值来填充所有的数据成员 重载的赋值运算符也是序列化所必需的。注意最后通过this指针返回 PErson类对象自 身的方法 在序列化成员函数 Serialize()中包含对象的保存和载入两部分。注意, CArchive类的 ”和“<”操作符并不支持所有的标准数据类型。支持的数据类型有: CObject、BYT WORD、int、LONG、 DWORD、foat和 double等。其他的类型的数据要进行序列化输入输 出时,需要将该类型的数据转化为上述几种类型之一方可 另外,在类的实现(类声明)文件开始处,还要加入 I MPLEMENT SERIAL()宏 IMPLEMENT SERIAL()宏用于定义一个从 CObject派生的可序列化类的各种参数。该宏 的第1和第2个参数分别代表可序列化的类名和该类的直接基类。第3个参数是对象的版本 号,可以是一个大于或等于零的整数。MFC序列化代码在将对象读入内存时检查版本号 如果磁盘文件上的对象的版本号和内存中的对象的版本号不一致,MFC将抛出一个 CArchive Exception类的异常,阻止程序读入一个不匹配版本的对象。 现在,我们就可以象使用标准MFC类一样使用 PErson类的序列化功能了 134编写独立的打印处理程序 MFC的打印功能由视图类的 OnPaint()成员函数完成。在缺省情况下, OnPaint()调 用 OnDraw()进行打印,这样就可实现“所见即所得”的打印效果。如果要求打印格式与 显示格式不同,那么就要重载 On Print()函数,自行编写打印代码 应用程序框架每打印一页调用 OnPaint()函数一次,这是为了方便输出页眉、页码等 与页面有关的信息。如果要打印的内容不只一页,则要在 On Print()函数中正确设置要打 印的内容 例135修改例13-3的吹泡泡程序,使其打印每个泡泡的数据值。打印格式为每页40
第 13 单元 文档读写与打印 - 263 - ar > m_IDnumber; ar >> m_strName; ar >> m_strNation; ar >> m_nSex; ar >> m_nAge; ar >> (int)m_bMarried; } } 分 析:MFC 在从磁盘文件载入对象状态并重建对象时,需要有一个缺省的不带任 何参数的构造函数以及一个重载的赋值运算符。序列化对象将用该构造函数生成一个对象, 然后调用 Serialize()函数,用重建对象所需的值来填充所有的数据成员。 重载的赋值运算符也是序列化所必需的。注意最后通过 this 指针返回 CPerson 类对象自 身的方法。 在序列化成员函数 Serialize()中包含对象的保存和载入两部分。注意,CArchive 类的 “>>”和“<<”操作符并不支持所有的标准数据类型。支持的数据类型有:CObject、BYTE、 WORD、int、LONG、DWORD、float 和 double 等。其他的类型的数据要进行序列化输入输 出时,需要将该类型的数据转化为上述几种类型之一方可。 另外,在类的实现(类声明)文件开始处,还要加入 IMPLEMENT_SERIAL()宏。 IMPLEMENT_SERIAL()宏用于定义一个从 CObject 派生的可序列化类的各种参数。该宏 的第 1 和第 2 个参数分别代表可序列化的类名和该类的直接基类。第 3 个参数是对象的版本 号,可以是一个大于或等于零的整数。MFC 序列化代码在将对象读入内存时检查版本号。 如果磁盘文件上的对象的版本号和内存中的对象的版本号不一致,MFC 将抛出一个 CArchiveException 类的异常,阻止程序读入一个不匹配版本的对象。 现在,我们就可以象使用标准 MFC 类一样使用 CPerson 类的序列化功能了。 13.4 编写独立的打印处理程序 MFC 的打印功能由视图类的 OnPrint()成员函数完成。在缺省情况下,OnPrint()调 用 OnDraw()进行打印,这样就可实现“所见即所得”的打印效果。如果要求打印格式与 显示格式不同,那么就要重载 OnPrint()函数,自行编写打印代码。 应用程序框架每打印一页调用 OnPrint()函数一次,这是为了方便输出页眉、页码等 与页面有关的信息。如果要打印的内容不只一页,则要在 OnPrint()函数中正确设置要打 印的内容。 [例 13-5] 修改例 13-3 的吹泡泡程序,使其打印每个泡泡的数据值。打印格式为每页 40
第13单元文档读写与打印 264 行,页眉为文档名,页脚为页号 说明:首先为视图类添加一个数据成员 m nLinePerPage,用来存放每页行数,并 在视图类 CMy View的构造函数中将 m nLine Per Page初始化为40 修改视图类成员函数 OnPrepareDO(),设置映射模式为 MM TWIPS。该模式为每英寸 1440点,很适合打印机输出。 程序:重载视图类的成员函数 OnPreparePrinting(),在其中添加计算打印页数的 代码 BOOL CMyView: OnPreparePrinting(CPrintInfo* pInfo) CMyDoc *pDoc GetDocumento int nPage Count pDoc->GetListSize o/m nLinePerPage if(pDoc->GetListSizeo%m nLinePerPage) pInfo->SetMaxPage(nPage Count) return DoPreparePrinting (pInfo 最后重载视图类的 On Print()函数并添加打印代码 void CMyView:: OnPrint( CDC* pDC, CPrintInfo* pInfo int nPage pInfo->m nCurPage //当前页号 tart=( nPage-1)* m nLinePerPage;//本页第一行 int nEnd =nStart+m nLinePerPage //本页最后一行 CFont font //设置字体 font. CreateFont (-280, 0, 0, 0, 400, FALSE, FALSE 0. ANSI CHARSET, OUT DEFAULT PRECIS CLIP DEFAULT PRECIS, DEFAULT QUALITY DEFAULT PITCH FF MODERN, Courier New") CFont **poldFont =(CFont *)(pDC->SelectObject(&font)) CRect rect Paper= pInfo-> m rectDraw;//取页面打印矩形 //页眉:页面顶端中央打印文档名称 CMy Doc *pDoc= GetDocumento ASSERT VALID(pDoc) CString str str Format("Bubble Report: %s",(LPCSTR)pDoc->GetTitleo) CSize sizeText pDC->GetTextExtent(str) CPoint point((rectPaper Width o-sizeText. cx)/2, 0) pDC->TextOut(point. x, point y, str)
第 13 单元 文档读写与打印 - 264 - 行,页眉为文档名,页脚为页号。 说 明:首先为视图类添加一个数据成员 m_nLinePerPage,用来存放每页行数,并 在视图类 CMyView 的构造函数中将 m_nLinePerPage 初始化为 40。 修改视图类成员函数 OnPrepareDC(),设置映射模式为 MM_TWIPS。该模式为每英寸 1440 点,很适合打印机输出。 程 序:重载视图类的成员函数 OnPreparePrinting(),在其中添加计算打印页数的 代码: BOOL CMyView::OnPreparePrinting(CPrintInfo* pInfo) { CMyDoc *pDoc = GetDocument(); int nPageCount = pDoc->GetListSize()/m_nLinePerPage; if(pDoc->GetListSize() % m_nLinePerPage) nPageCount ++; pInfo->SetMaxPage(nPageCount); return DoPreparePrinting(pInfo); } 最后重载视图类的 OnPrint()函数并添加打印代码: void CMyView::OnPrint( CDC* pDC, CPrintInfo* pInfo ) { int nPage = pInfo->m_nCurPage; // 当前页号 int nStart = (nPage-1)*m_nLinePerPage; // 本页第一行 int nEnd = nStart+m_nLinePerPage; // 本页最后一行 CFont font; // 设置字体 font.CreateFont(-280, 0, 0, 0, 400, FALSE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_MODERN, "Courier New"); CFont *pOldFont = (CFont *)(pDC->SelectObject(&font)); CRect rectPaper = pInfo->m_rectDraw; // 取页面打印矩形 // 页眉: 页面顶端中央打印文档名称 CMyDoc *pDoc = GetDocument(); ASSERT_VALID(pDoc); CString str; str.Format("Bubble Report: %s", (LPCSTR)pDoc->GetTitle()); CSize sizeText = pDC->GetTextExtent(str); CPoint point((rectPaper.Width()-sizeText.cx)/2, 0); pDC->TextOut(point.x, point.y, str);
第13单元文档读写与打印 point x rectPaper left /打印页眉下划线 Text pDC->Move To(point) pDC->Line To (point) /打印表头 str. Format("%6.6s%6.6s‰6.6s%6.6s‰6.6s", Index,"Left",Top (point x, point y, str) TEXTMETRIC tm //取当前字体有关信息 pDC->GetTextMetrics(&tm) int nHeight tm. tmHeight+tm. tmExternalleading //下移1/4英寸 for(int 打印表体 f(i >= pDoc->GetListSize o) str Format(%6d %6d %6d %6d %6d", i+ pDoc->GetBubble(i). tol pDoc->GetBubble(i). right, pDoc-Get Bubble(i). bottom) pDC->TextOut(point. x, point y, str) //在页面底部中央打印页号 str Format("-%d izet >GetTextExtent(str) pointx =(rectPaper WidthO-size Text cx)/2 point y rectPaper Height (+size Text. cy pDC->TextOut(point. x, point y, str) //释放字体对象 无标趣攻汽 pC-> Selector jec(p0ld回啊"凹 输入输出:使用鼠标左键在窗口吹
第 13 单元 文档读写与打印 - 265 - point.x = rectPaper.left; // 打印页眉下划线 point.y = rectPaper.top-sizeText.cy; pDC->MoveTo(point); point.x = rectPaper.right; pDC->LineTo(point); // 打印表头 str.Format("%6.6s %6.6s %6.6s %6.6s %6.6s", "Index", "Left", "Top", "Right", "Bottom"); point.x = 720; point.y -= 720; pDC->TextOut(point.x, point.y, str); TEXTMETRIC tm; // 取当前字体有关信息 pDC->GetTextMetrics(&tm); int nHeight = tm.tmHeight+tm.tmExternalLeading; point.y -= 360; // 下移 1/4 英寸 for(int i=nStart; i= pDoc->GetListSize()) break; str.Format("%6d %6d %6d %6d %6d", i+1, pDoc->GetBubble(i).left, pDoc->GetBubble(i).top, pDoc->GetBubble(i).right, pDoc->GetBubble(i).bottom); point.y -= nHeight; pDC->TextOut(point.x, point.y, str); } // 在页面底部中央打印页号 str.Format("- %d -", nPage); sizeText = pDC->GetTextExtent(str); point.x = (rectPaper.Width()-sizeText.cx)/2; point.y = rectPaper.Height()+sizeText.cy; pDC->TextOut(point.x, point.y, str); // 释放字体对象 pDC->SelectObject(pOld Font); } 输入输出:使用鼠标左键在窗口吹