250 likes | 325 Views
第九章 文件与打印. 9.1 文件的存储与读取 9.2 打印 9.3 实例:所见即所得的打印程序. 本章要点 本章介绍如何进行文件的读写和打印。文件的保存和打印是许多应用程序必备的功能,我们在这一章里首先介绍了和文件保存有关的 CFile 类和序列化这一重要概念。然后讲述了和打印有关的 CPrint Info 类和与打印及预览相关的函数。. 返回. 9.1 文件的存储与读取. ( 1 ) 使用 CFile 打开和关闭文件
E N D
第九章 文件与打印 • 9.1 文件的存储与读取 • 9.2 打印 • 9.3 实例:所见即所得的打印程序 本章要点 本章介绍如何进行文件的读写和打印。文件的保存和打印是许多应用程序必备的功能,我们在这一章里首先介绍了和文件保存有关的CFile类和序列化这一重要概念。然后讲述了和打印有关的CPrint Info类和与打印及预览相关的函数。 • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 (1)使用CFile打开和关闭文件 BOOL Open(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL); 参数lpszFileName:为欲打开的文件名,文件名可以包含路径和文件名两部分。如“d:\\bak\\test.txt”, 如果此字符串不包含文件路径,如“test.bak”,则系统默认为当前路径,即生成的可执行文件所在目录。 参数nOpenFlags:用于设置访问模式,指定当打开文件时进行的动作,可以将以下所列模式用按位或“|”操作符连接起来。至少应有一个访问模式,modeCreate是可选的。以下是常用参数列表: CFile::modeCreate:调用构造函数构造一个新文件。 CFile::modeRead : 打开文件仅供读。 CFile::modeReadWrite: 打开文件供读/写。 CFile::modeWrite: 打开文件仅供写。 CFile::typeText: 设置文本文件模式(只能用在子类中)。 CFile::typeBinary:设置二进制文件模式(只能用在子类中)。 参数 pError:是一个异常类的指针,可通过该类和函数的返回值来确定函数是否调用成功。 • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 例: 如要以只读方式打开当前目录的“test.txt”文件时,一个可能的代码如下: CFile MyFile; cFileException e; if( !MyFile.open ("test.txt",CFile::modeRead,&e) ) { e->ReportError ( ) ; } 当然,也可直接使用CFile类的第三个构造函数来创建CFile类对象并同时打开一个文件,如上面的代码可改为: try { CFile MyFile("test.txt" ,CFile::modeRead) ; …… }catch(CFileException *e) { e->ReportError ( ) ; e->Delete ( ) ; } 关闭一个打开的文件,可使用Close函数,该函数不带参数。用户应该养成这样的习惯,打开一个文件并使用完毕后,使用Close函数关闭该文件。 • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 (2)文件的读写操作 virtual UINT Read (void* lpBuf, UINT nCount) ; throw(CFileException); Read函数返回值是传输到缓冲区的字节数。 参数lpBuf:指向用户提供的缓冲区以接收从文件中读取的数据。 参数nCount:为可以从文件中读出字节数的最大值。 注意:对所有CFile类,如果到达文件尾,则返回值可能比nCount小。 virtual void Write(const void* lpBuf, UINT nCount); throw (CFileException); Write函数的参数与Read函数的参数类似。 参数lpBuf:指向用户提供的缓冲区,包含将写入文件中的数据。 参数nCount:从缓冲区内传输的字节数。 Write在几种情况下均产生异常,包括磁盘满的情况、磁盘为写保护状态等。 注意:CFile类并没有提供类似EOF之类的文件结束标志,所以文件的结束是根据Read函数的返回值来判断的。Read函数返回的是实际读出的字符数,当返回0时,则表示文件已读完。 • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 举个例子,假设现在要把当前目录下的文件test1.txt里的数据添加到testf2.txt文件尾,则一个可能的实现代码如下: try { CFile File1 ("test1.txt",CFile::modeRead) ; CFile File2 ( "test2.txt",CFile::modeWrite) ; " char pBuf[256] ; int strLen =0; File2.SeekToEnd( ) ; While((strLen=File1.Read(pBuf,256) )>0) File2.Write(pBuf. strLen) ; File1.Close( ) ; File2.Close( ) ; }catch(CFileException *e) { e.ReportError ( ) ; } • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 (3)文件的定位 文件定位涉及到一个基本的概念:文件指针。文件指针是指文件当前的读写位置。如打开一个文件用于读取时,文件指针通常指向文件的开头的第一个字符;以后每读一个字符,文件指针会自动向后偏移一个位置,以指向下一个字符。CFile类提供了很多成员函数用于提供文件指针的信息以及手动设置文件指针的位置,从而可以实现按需要从文件的任意位置读写文件。 成员函数GetLength用于返回打开文件的字节长度,GetPosition函数用于返回文件指针的当前位置。这两个函数的原型如下: virtual ULONGLONG GetLength( ) const; virtual ULONGLONG GetPosition( ) const; 有一组函数提供文件定位功能,它们是SeekToBegin、SeekToEnd和Seek函数。函数SeekToBegin用于把文件指针移动到文件的开始位置,它的函数原型如下: void SeekToBegin( ); 函数SeekTOEnd用于把文件指针移动到文件尾。若要在文件末尾添加数据,则常用到这一个函数。它的原型如下: ULONGLONG SeekToEnd( ); 其中它的返回值为文件的字节长度。 • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 最后还有一个功能最强大的函数是Seek函数,它可以把文件指针移动到文件的任何位置。它的原型如下: virtual ULONGLONG Seek(LONGLONG loff,UINT nFrom); 参数loff用于指定文件指针的偏移量,而nFrom用于指定文件指针的起始位置,下表列出了它的几种取值及其含义。其返回值表示定位后文件指针离文件头的偏移量。 需要注意的是,如果用Seek函数定位后文件指针离文件头的偏移量要大于文件的长度,则该函数并不会产生异常,相反,文件的长度会自动扩充成文件指针的偏移量值。如假设文件长度为1000字节,而用Seek函数把文件指针定位到离文件头偏移1050字节的位置时,文件长度便自动变为1050字节了。 • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 (4)序列化与CArchive类 序列化又叫串行化,即Serialize。序列化是实现文件保存的必经过程。MFC的CObject派生类都是支持序列化的。如果用户自定义一个需要保存的类,就必须使之序列化。 要让用户定义的类支持序列化,一般分为5步: (1)从CObject或其派生类中派生出用户的类。 (2)重载Serialize()成员函数,加入必要的代码,用以保存对象的数据成员到CArchive对象以及从CArchive对象载入对象的数据成员状态。 (3)在类声明文件中,加入DECLARE_SERIAL宏。编译时,编译器将扩充该宏,这是序列化对象所必需的。 (4)定义一个不带参数的构造函数。 (5)在实现文件中加入IMPLEMENT_SERIAL宏。 • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 例如,下面用户自定义的一个CObject的派生类,叫做CPInfo类,它可以保存一些个人注册信息,比如姓名、收入、人种、工作等。 class CPInfo:public CObject { public: DECLARE_SERIAL(CPInfo) CPInfo(){}; //必须提供一个不带任何参数的空的构造函数 public: CString strIncome; CString strKind; BOOL bMarried; CString strName; int nSex; CString strUnit; int nWork; UINT nAge; void Serialize(CArchive&); } • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 MFC在从磁盘文件载入这些对象状态并重建对象时,这里需要有一个默认的不带任何参数的构造函数,序列化对象将用该构造函数生成一个对象,然后调用Serialize()函数,用重建对象所需的值来填充对象的所有数据成员变量。构造函数既可以声明为public,也可以声明为protected或private。 从上面的例子中我们可以注意到,序列化函数所带的参数是一个文件类的对象,我们一般使用CArchive类。 CArchive类提供了一个IsStoring()成员函数,它指示了是将数据保存到磁盘文件还是从磁盘文件载入对象。例如,实现上面CPInfo类的序列化: void CPInfo::Serialize(CArchive& ar) { //首先调用基类的Serialize()方法。 CObject::Serialize(ar); if(ar. IsStoring()) { ar<<strIncome; ar<<strKind; ar<<(int)bMarried; ar<<strName; ar<<nSez; ar<<strUnit; • 返回 民政学院软件学院 蒋国清
9.1 文件的存储与读取 ar<<nWork; ar<<(WORD)nAge; } else { ar>>strIncome; ar>>strKind; ar>>(int)bMarried; ar>>strName; ar>>nSex; ar>>strUnit; ar>>nWork; ar>>(WORD)nAge; } } 很显然,对象的序列化实际上是通过调用对象中的数据成员的序列化来完成的。 • 返回 民政学院软件学院 蒋国清
9.2 打 印 (1)建立一个支持打印的框架 先看看建立框架时如何使之支持打印和预览功能,在应用程序向导创建多文档应用程序的【高级功能】选项面板中,有一个【打印和打印预览】复选框,将其选中即可,这时打印功能就可以被启用,如图9-1所示。 • 返回 民政学院软件学院 蒋国清
9.2 打 印 事实上,即使我们没有刻意地去选择选择这一项,应用程序向导也能够帮助我们完成,因为默认的选项就是支持打印。这里只是通过再次回顾,以确定和理解我们在哪一步完成了建立支持打印的应用程序框架的工作。 我们发现,在生成的应用程序框架界面里,【文件】菜单里面多了3个【打印】、【打印预览】和【打印设置】这3个命令,如图9-2所示。 • 返回 民政学院软件学院 蒋国清
9.2 打 印 一般来说,一个MFC应用程序的打印工作一般是按如下步骤进行的: (1) 首先显示Print对话框。 (2) 创建一个与当前打印机设置相匹配的图形设备(CDC)对象。 (3) 设置要打印的页数,调用CDC::StartDoc开始打印。 (4)用CDC::StartPage开始打印一页;调用视图的OnDraw()方法打印输出一页内容 用CDC::EndPage结束一页的打印,循环输出全部内容。 (5) 用CDC::EndDoc结束打印。 (6) 视图作打印的清理工作。 需要注意的是,应用程序向导自动生成了上面打印和打印预览的代码。但是许多情况下,并不能符合要求。这是因为: 首先,打印机和窗口(屏幕)显示的分辨率不同。打印机的分辨率用单位平方英寸面积上有多少黑点来描述,屏幕分辨率用单位面积有多少像素点来表示。在编辑器程序中,使用的映射模式为MM_TEXT。在这种模式下,一个逻辑单位对应于一个像素点。Windows又是按照逻辑单位来进行绘图的。这样,根据MM_TEXT模式的逻辑单位(实际上也就是像素数目)决定比例的原则打印出来的内容自然要比屏幕上看到的要小得多。 提示:在初始化视图OnInitialUpdate时候,在选择绘图的映射模式上,不要采用以前使用的默认的MM_TEXT模式,而要采用MM_LOENGLISH。 • 返回 民政学院软件学院 蒋国清
9.2 打 印 此外,窗口和打印机对边界的处理不同。窗口可以看做是无边界的,可以在窗口外面画,而不会引起错误,窗口会自动剪裁超出边界的图形。但打印机却不同,它是按页打印的。打印输出时必须自己处理分页和换页,如果不做这样的处理的话,行和行之间就会叠加起来。 要正确打印输出屏幕上的内容,就必须解决以上两个问题。对于前一个问题,有两种方法可以解决:一是利用SetMapMode(int nMode)设置别的映射模式,比如采用MM_LOENGLISH,不用像素而是采用英寸来衡量。 对于第2个问题,要处理打印分页、换页,就必须修改框架处理打印消息的默认行为,在其中计算和换页。此外,我们还希望在打印时在页眉处能够输出标题(使用文件名作为标题),在页脚处输出页码。 • 返回 民政学院软件学院 蒋国清
9.2 打 印 (2)CPrintInfo类与打印相关函数 在完成一个支持打印的程序框架的创建以后,我们很快就会发现,在与打印相关的函数中会有一个CPrintInfo类型的对象或该对象指针。这个对象用来维护打印作业或打印预览作业的信息,用于应用程序视图与MFC的内置打印函数之间的信息交换。在打印的时候,这个对象在MFC框架类和视图类之间传递。下表列出了CPrintInfo类的最常用数据成员。 • 返回 民政学院软件学院 蒋国清
9.2 打 印 下面介绍一下MFC的打印体系结构,即打印功能是怎样通过相关函数实现的。 框架的打印文档功能是从OnPreparePrinting(CPrintInfo* pInfo)开始的,在默认的情况下,它只是简单地调用视图的DoPreparePrinting()函数。DoPreparePrinting()显示Print对话框,并创建与打印机相匹配的设备上下文。如果要想改变打印机初始设置,可以在这里改。例如,我们希望某个文档分两页打印:第1页为封面,打印文档名字;第2页输出文档内容,并在页眉上打印文档名字。默认设置下,使用1作为第1页编号,用0xFFFF作为文档的最后一页编号。因为要求分两页打印输出,因此要在这里设置打印页数。要设置打印页数,可以调用函数CPrintInfo::SetMaxPage(nMaxPage)。通过设置CPrintInfo对象的m_nNumPreviewPage成员变量,还可以将预览页数也设置为两页。参考如下代码: BOOL CDrawView::OnPreparePrinting(CPrintInfo* pInfo) { pInfo->SetMaxPage(2); //这个文档有两页 //并且处页作为标题页,第2页为需要打印的页 BOOL bRet = DoPreparePrinting(pInfo); //默认设置 pInfo->m_nNumPreviewPages=2; //每次预览两页 return bRet; } • 返回 民政学院软件学院 蒋国清
9.2 打 印 DoPreparePrinting显示Print对话框。返回时,CPrintInfo结构包含了用户所指定的值,包括起止页号、最大页号、最小页号等。 OnBeginPrinting()在OnPreparePrinting()被调用之后实际打印之前调用。OnBeginPrinting()用于分别GDI资源。 OnPrepareDC用做屏幕显示时,在绘图前调整DC。在用于打印时,OnPrepareDC也完成类似功能。 OnPrint完成真正的打印一页文档的工作。它把一个打印机设备上下文传给视图类对象的OnDraw函数,由OnDraw负责打印输出。可以把那些适合于打印但是不适合于屏幕输出的工作,如打印页眉和页脚,放在OnPrint()的重载中完成,然后再调用OnDraw完成打印和显示都需要的工作。OnPrint()不是由应用程序向导自动生成的,要用类向导为视图类对象增加OnPrint()方法,然后添加绘图程序的特殊打印打印代码。参考以下代码,在OnPrint中加入打印页眉和页脚的代码。 void CDrawView::OnPrint(CDC* pDC, CPrintInfo* pInfo) { //第1页是封面 if (pInfo->m_mCurPage = =1) { PrintTitlePage(pDC, pInfo); • 返回 民政学院软件学院 蒋国清
9.2 打 印 //在第1页上只打印标题 return; } //打印页眉 CString strHeader=GetDocument()->GetTitle(); PrintPageHeader(pDC, pInfo, strHeader); //自定义的打印页眉函数 //调整正文区域位置和大小 pDC->SetWindowOrg(pInfo->m_rectDraw. left. –pInfo->m_rectDraw, top); //打印正文 OnDraw(pDC); } OnPrint()首先根据CPrintInfo类的pInfo中m_nCurPage(保存当前打印页号信息)判断当前打印是不是第1页。如果是第1页,就打印输出封面。否则,首先调用PrintPageHeader打印页眉,然后用SetWindowOrg调整打印输出的原点位置。m_rectDraw又是CPrintInfo结构的一个重要数据成员,它保存的是打印输出的矩形边界。最后将与打印机匹配的设备上下文传给OnDraw,由OnDraw在打印面上输出。 注意:这里使用的映射模式为MM_LOENGLISH,它的Y轴方向是向上递增的。 • 返回 民政学院软件学院 蒋国清
9.3实例:所见即所得的打印程序 实例说明: MFC几乎做了打印文档所需要的全部工作,而且,打印预览已与由MFC应用程序向导所生成的应用程序合并在一起,这使得用户几乎无需编写任何代码,即可创建一个支持打印和打印预览功能的应用程序。以下将演示如何创建一所见即所得的打印程序。 本实例创建一个具有全部打印功能应用程序,该程序不仅可以在它的主窗口显示它的数据(矩形),而且在一个打印预览的窗口中和打印机上都可以显示和打印输出。用户可以通过鼠标的左、右击来增加和减少矩形的数目。该程序在打印预览窗口中的效果如下图所示。 学习目标: 在本例中,用户主要应学会如何创建一个支持打印和打印预览功能的应用程序,如何设置设备环境的映射模式,以及如何动态增减矩形的个数 • 返回 民政学院软件学院 蒋国清
9.3实例:所见即所得的打印程序 (1)启动Visual C++.NET,新建一个Visual C++项目,选用MFC应用程序模板,项目名称为PrintExp。 (2)在应用程序向导的【应用程序类型】选项中选择【单文档】,同时确保【高级功能】选项中选中【打印与打印预览】项。最后单击【完成】按钮完成项目的建立。 分析:此时所创建的应用程序已具有了全部的打印功能,以下将主要为此应用程序 构造输出数据(矩形)。 (3)为CPrintExpDoc类添加成员变量m_numRects,用于保存显示矩形的个数。 Public: int m_numRects; (4)为CPrintExpDoc类的构造函数添加代码,用于设置初始时显示矩形的个数。 CPrintExpOoc : : CPrintExpDoc ( ) { m_numRects=5; } (5)为CPrintExpView类添加鼠标单击消息的映射函数。在【类视图】窗口中,右击 CPrintExpView类调出【属性】窗口,选择【消息】选项卡,从中添加WM_LBUTTONDOWN与WM_RBUTTONDOWN的消息处理函数。 (6)编写OnLButtonDown()函数,用于单击鼠标左键时,增加显示矩形的数目。 voidCPrintExpView: : OnLButtonDown (UINT nFlags, CPoint point) • 返回 民政学院软件学院 蒋国清
9.3实例:所见即所得的打印程序 { //TODO:在此添加消息处理程序代码和/或调用默认值 //获取CPrintExpDoc类的指针 CPrintExpDoc * pDoc=GetDocument ( ) ; ASSERT_VALID (pDoc) ; //使显示矩形数目加一 pDoc->m_numRects++ ; //更新显示 Invalidate () ; CView: :OnLButtonDown (nFlags, point); } (7)编写OnRButtonDown()函数,用于单击鼠标右键时,减少显示矩形的数目。 void CPrintExpView: :OnRButtonDown (UINT nFlags, CPoint point} { // TODO:在此添加消息处理程序代码和/或调用默认值 //获取CPrintExpDoc类的指针 CPrintExpDoc " pDocEGetDocument ( ) ; ASSERT VALID (pDoc) ; • 返回 民政学院软件学院 蒋国清
9.3实例:所见即所得的打印程序 //使显示矩形数目减一 if (pDoc->m_numRects>0 ) { pDoc->m_numRects-- ; //更新显示 Invalidate( ) ; } CView: :OnRButtonDown (nFlags, point) ; } (8)编写CPrintExPView类的OnDraw()函数,用于绘制矩形和显示矩形数目。 void CPrintExpView: :OnDraw (CDC* pDC) { CPrintExpDoc* pDoc=GetDocument () ; ASSERT VALID (pDoc) ; //TODO:在此处为本机数据添加绘制代码 //设置设备环境的映射模式 pDC->SetMapMode (MM_LOENGLISH) ; • 返回 民政学院软件学院 蒋国清
9.3实例:所见即所得的打印程序 //显示矩形的数目 CString numbers; numbers . Format ( "&d" , pDoc->m_numRects ) ; pDC->TextOut (300, -100, numbers) ; //绘制矩形 ror (int x=0 ; x<pDoc->m_numRects ; ++x) { pDC->Rectangle (20, - (20+x*220) , 220, - (220+x*220) ) ; }} 分析:映射模式用于定义逻辑坐标的单位与设备坐标间的关系。在默认的映射模式 MM_TEXT下,把每个逻辑单位映射成一个设备像素,X轴正方向朝右,Y轴正方向朝下。但在MM_TEXT映射模式下,有可能使打印的文档和屏幕上显示的文档不同,这是由于屏幕上的像素和打印机上的点尺寸不同。为了避免当打印机和屏幕的一页容纳不同的像素时引起的问题,一种较好的图形映射方式为MM_LOENGLISH,它使用百分之一英寸而非一点或一个像素作为计量单位,在这种映射模式下,X轴正方向朝右,Y轴正方向朝上。 至此,程序编写完成,编译并运行它,在应用程序中,试着单击鼠标左、右键以改变显示矩形的数目。 • 返回 民政学院软件学院 蒋国清
9.3实例:所见即所得的打印程序 当用户单击【文件】菜单下的【打印预览】项时,将会显示【打印预览】窗口。 当窗口中显示的矩形数目超过6时,在一页中将无法显示所有的矩形。此时,若用户在【打印预览】窗口选择【两页】按钮,发现此程序并不能支持多页打印功能,第二页尚为空,如下图所示。 • 返回 民政学院软件学院 蒋国清