第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 nUbble Count for (int i=0: i<m nUbble Count: i++) ar < m rectBubble[i] for (int i=0: i<m nBubbleCount: i++) 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_nBubbleCount; i++) ar << m_rectBubble[i]; } else { ar >> m_nBubbleCount; for(int i=0; i<m_nBubbleCount; i++) ar >> 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: i<pDoc->G 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; i<pDoc->GetListSize(); 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 的打印效果