多页文档
本文介绍 Windows 打印协议并说明如何打印包含多个页面的文档。 本文包含以下主题:
打印协议
为了打印多页文档,框架和视图将按以下方式交互。 首先,框架显示“打印”对话框,为打印机创建设备上下文,并调用 CDC 对象的 StartDoc 成员函数。 然后,对于文档的每一页,框架将调用 CDC
对象的 StartPage 成员函数,指示视图对象打印该页,并调用 EndPage 成员函数。 如果在开始打印特定页之前必须更改打印机模式,视图将调用 ResetDC,这将更新包含新打印机模式信息的 DEVMODE 结构。 打印完整个文档后,框架将调用 EndDoc 成员函数。
重写视图类函数
CView 类定义框架在打印期间调用的若干成员函数。 通过在视图类中重写这些函数,便能在框架的打印逻辑和您的视图类的打印逻辑之间建立连接。 下表列出了这些成员函数。
CView 的可重写的打印函数
名称 | 重写的原因 |
---|---|
OnPreparePrinting | 在“打印”对话框中插入值,尤其是文档的长度 |
OnBeginPrinting | 分配字体或其他 GDI 资源 |
OnPrepareDC | 为给定页调整设备上下文的特性,或执行打印时分页 |
OnPrint | 打印给定页 |
OnEndPrinting | 释放 GDI 资源 |
您也可以在其他函数中执行与打印有关的处理,但这些函数是驱动打印过程的函数。
下图演示了打印过程涉及的步骤,并显示了 CView
的每个打印成员函数的调用位置。 本文的其余部分将更详细地说明大部分这些步骤。 打印过程的其他部分在分配 GDI 资源一文中有述。
打印循环
分页
框架将很多有关打印作业的信息存储在 CPrintInfo 结构中。 CPrintInfo
中的某些值与分页有关;这些值是可访问的,如下表所示。
存储在 CPrintInfo 中的页码信息
成员变量或 函数名 |
引用的页码 |
---|---|
GetMinPage /SetMinPage |
文档的第一页 |
GetMaxPage /SetMaxPage |
文档的最后一页 |
GetFromPage |
要打印的第一页 |
GetToPage |
要打印的最后一页 |
m_nCurPage |
当前打印的页 |
页码从 1 开始,即第一页的页码为 1,而不是 0。 有关 CPrintInfo 的这些成员和其他成员的详细信息,请参阅“MFC 参考”。
在打印过程开始时,框架将调用视图的 OnPreparePrinting 成员函数,并传递指向 CPrintInfo
结构的指针。 应用程序向导提供调用 DoPreparePrinting(CView
的另一个成员函数)的 OnPreparePrinting
的实现。 DoPreparePrinting
是显示“打印”对话框并创建打印机设备上下文的函数。
此时,应用程序无法知道文档中有多少页。 它对文档的第一页和最后一页的页码使用默认值 1 和 0xFFFF。 如果你知道文档有多少页,请在将它发送给 DoPreparePrinting
之前重写 OnPreparePrinting
,并为 CPrintInfo
结构调用 [SetMaxPage]--brokenlink--(reference/cprintinfo-class.md#setmaxpage)。 这样,您便可以指定文档的长度。
DoPreparePrinting
随后显示“打印”对话框。 当它返回时,CPrintInfo
结构包含用户指定的值。 如果用户希望仅打印选定范围的页,他/她可以在“打印”对话框中指定开始和结束页码。 框架将使用 CPrintInfo 的 GetFromPage
和 GetToPage
函数检索这些值。 如果用户未指定页范围,框架将调用 GetMinPage
和 GetMaxPage
并使用返回的值来打印整个文档。
对于文档要打印的每一页,框架将在视图类中调用两个成员函数(OnPrepareDC 和 OnPrint),并向每个函数传递两个参数:指向 CDC 对象的指针和指向 CPrintInfo
结构的指针。 每当框架调用 OnPrepareDC
和 OnPrint
时,都会在 CPrintInfo
结构的 m_nCurPage 成员中传递不同的值。 这样,框架就能告知视图应该打印的页。
OnPrepareDC 成员函数还用于屏幕显示。 它在绘制发生前对设备上下文进行调整。 OnPrepareDC
在打印中充当类似的角色,但有两个区别:首先,CDC
对象表示的是打印机设备上下文而不是屏幕设备上下文;其次,CPrintInfo
对象作为第二个参数传递。 (当为屏幕显示调用 OnPrepareDC
时,此参数为 NULL。)重写 OnPrepareDC
会根据要打印的页面对设备上下文进行调整。 例如,您可以移动视区原点和剪辑区域以确保打印文档的相应部分。
OnPrint 成员函数执行页的实际打印。 如何执行默认打印一文演示了框架如何使用打印机设备上下文调用 OnDraw 来执行打印。 更准确地说,框架使用 OnPrint
结构和设备上下文调用 CPrintInfo
,而 OnPrint
将设备上下文传递到 OnDraw
。 重写 OnPrint
以执行只应在打印期间进行并且不是用于屏幕显示的任何渲染。 例如,打印页眉或页脚(有关详细信息,请参阅页眉和页脚一文)。 然后,从 OnDraw
的重写调用 OnPrint
以执行屏幕显示和打印共用的渲染。
OnDraw
同时为屏幕显示和打印执行渲染意味着你的应用程序是 WYSIWYG:“所见即所得”。但是,此处假定你没有编写 WYSIWYG/所见即所得应用程序。 例如,想象一下这样一个文本编辑器:使用粗体字体进行打印但显示控件代码以在屏幕上指示粗体文本。 在这种情况下,您应严格使用 OnDraw
进行屏幕显示。 当重写 OnPrint
时,请用对 OnDraw
的调用替换对单独的绘图函数的调用。 该函数将使用您不在屏幕上显示的特性,按照文档在纸上显示的方式绘制文档。
打印机页与文档页
提到页码,有时候必须区分打印机的页概念与文档的页概念。 从打印机的角度来看,一页就是一张纸。 但是,一张纸不一定等同于一页文档。 例如,如果打印的是要对折纸张的新闻稿,那么一张纸可能会并排放置文档的第一页和最后一页。 同样,如果打印的是电子表格,那么文档完全不包含页。 相反,一张纸可能包含 1 至 20 行,6 至 10 列。
CPrintInfo 结构中的所有页码都是指打印机页。 框架将为要通过打印机的每张纸调用一次 OnPrepareDC
和 OnPrint
。 当重写 OnPreparePrinting 函数以指定文档的长度时,必须使用打印机页。 如果存在一对一的对应关系(即一个打印机页等同于一个文档页),则此操作很容易。 另一方面,如果文档页和打印机页不直接对应,则必须对其进行相应的转换。 例如,考虑一下打印电子表格。 当重写 OnPreparePrinting
时,必须计算打印整个电子表需要多少张纸,然后在调用 SetMaxPage
的 CPrintInfo
成员函数时使用该值。 同样,当重写 OnPrepareDC
时,必须将 m_nCurPage 转换为显示在该特定纸张中的行和列的范围,然后相应地调整视区原点。
打印时分页
在某些情况下,视图类不能预先知道文档在实际打印前要等待多长时间。 例如,假定应用程序不是所见即所得的,那么文档在屏幕上的长度与其打印时的长度就不对应。
这将导致为视图类重写 OnPreparePrinting 时出现问题:你无法将值传递到 CPrintInfo 结构的 SetMaxPage
函数,因为你不知道文档的长度。 如果用户未“打印”对话框指定停止处的页码,框架就不知道何时停止打印循环。 确定何时停止打印循环的唯一方法是输出文档并查看它何时结束。 视图类必须在文档打印期间检查其末尾,然后告知框架何时到达末尾。
框架依赖视图类的 OnPrepareDC 函数以告知视图类何时停止。 每次调用 OnPrepareDC
后,框架都将检查称为 m_bContinuePrinting 的 CPrintInfo
结构的成员。 默认值为 TRUE。只要保持此值,框架就会继续打印循环。 如果此值设置为 FALSE,框架就会停止打印循环。 执行打印时分页,重写 OnPrepareDC
以检查是否已到达文档的末尾,然后在到达末尾时将 m_bContinuePrinting 设置为 FALSE。
如果当前页大于 1,OnPrepareDC
的默认实现会将 m_bContinuePrinting 设置为 FALSE。 这意味着,如果未指定文档的长度,框架将假定文档的长度是一页。 这样的一个后果是,您在调用基类版本的 OnPrepareDC
时必须小心。 不要假设在调用基类版本后 m_bContinuePrinting 将为 TRUE。