TN030:自定义打印和打印预览
注意
以下技术说明在首次包括在联机文档中后未更新。 因此,某些过程和主题可能已过时或不正确。 要获得最新信息,建议你在联机文档索引中搜索热点话题。
本说明介绍了自定义打印和打印预览的过程,并描述了 CView
中使用的回调例程以及 CPreviewView
的回调例程和成员函数的用途。
问题
MFC 为大多数打印和打印预览需求提供了完整的解决方案。 在大多数情况下,几乎不需要额外的代码就可拥有能够打印和预览的视图。 但有一些方法可以优化打印,这需要开发人员付出巨大的努力,并且某些应用程序需要向打印预览模式添加特定的用户界面元素。
高效打印
当 MFC 应用程序使用标准方法打印时,Windows 会将所有图形设备接口 (GDI) 输出调用定向到内存中元文件。 调用 EndPage
时,Windows 会对打印机需要打印一页的每个物理带播放一次图元文件。 在此呈现过程中,GDI 经常查询中止过程以确定它是否应继续。 通常中止过程允许处理消息,以便用户可以使用打印对话框中止打印作业。
遗憾的是,这可能导致打印过程变慢。 如果应用程序中的打印速度必须快于使用标准技术所能实现的速度,则必须实现手动分段。
打印分段
要手动分段,必须重新实现打印循环,这样每页才能多次调用 OnPrint
(每个带一次)。 打印循环在 viewprnt.cpp 中的 OnFilePrint
函数中实现。 在 CView
派生类中,重载此函数,以便用于处理打印命令的消息映射条目调用打印函数。 复制 OnFilePrint
例程并更改打印循环以实现分段。 你可能还需要将带状矩形传递给打印函数,以便根据要打印的页面部分优化绘图。
其次,必须在绘制带时经常调用 QueryAbort
。 否则中止过程不会被调用,用户将无法取消打印作业。
打印预览:带用户界面的电子纸张
从本质上讲,打印预览尝试将显示转换为打印机的仿真。 默认情况下,主窗口的工作区用于在窗口中完整显示一页或两页。 用户可以放大页面的某个区域以更详细地查看该部分。 借助其他支持,用户甚至可以在预览模式下编辑文档。
自定义打印预览
本说明仅涉及修改打印预览的一个方面:向预览模式添加 UI。 可进行其他修改,但此类更改不在本讨论范围内。
向预览模式添加 UI
从
CPreviewView
中派生一个视图类。为所需的 UI 方面添加命令处理程序。
如果要向显示添加视觉方面,请重写
OnDraw
并在调用CPreviewView::OnDraw
后执行绘图。
OnFilePrintPreview
这是打印预览的命令处理程序。 其默认实现如下:
void CView::OnFilePrintPreview()
{
// In derived classes, implement special window handling here
// Be sure to Unhook Frame Window close if hooked.
// must not create this on the frame. Must outlive this function
CPrintPreviewState* pState = new CPrintPreviewState;
if (!DoPrintPreview(AFX_IDD_PREVIEW_TOOLBAR, this,
RUNTIME_CLASS(CPreviewView), pState))
{
// In derived classes, reverse special window handling
// here for Preview failure case
TRACE0("Error: DoPrintPreview failed");
AfxMessageBox(AFX_IDP_COMMAND_FAILURE);
delete pState; // preview failed to initialize, delete State now
}
}
DoPrintPreview
将隐藏应用程序的主窗格。 控件条(如状态栏)可以通过在 pState->dwStates 成员中进行指定来保留(这是位掩码,各个控件条的位由 AFX_CONTROLBAR_MASK( AFX_IDW_MYBAR) 定义)。 窗口 pState->nIDMainPane 是将自动隐藏和重新显示的窗口。 然后 DoPrintPreview
将为标准预览 UI 创建按钮栏。 如果需要特殊窗口处理,如隐藏或显示其他窗口,则应在调用 DoPrintPreview
之前完成。
默认情况下,当打印预览完成时,它会将控件条恢复到原始状态,并使主窗格可见。 如果需要特殊处理,则应在重写 EndPrintPreview
的过程中完成。 如果 DoPrintPreview
失败,还将提供特殊处理。
使用以下值调用 DoPrintPreview:
预览工具栏的对话框模板的资源 ID。
指向可执行打印预览的打印的视图的指针。
预览视图类的运行时类。 这将在 DoPrintPreview 中动态创建。
CPrintPreviewState 指针。 请注意,CPrintPreviewState 结构(如果应用程序需要保留更多状态,则为派生结构)不得在框架上创建。 DoPrintPreview 是无模式的,在调用 EndPrintPreview 之前,此结构必须存在。
注意
如果打印支持需要单独的视图或视图类,则应将指向该对象的指针作为第二个参数传递。
EndPrintPreview
调用此项可终止打印预览模式。 通常需要移动到文档中上次在打印预览中显示的页面。 EndPrintPreview
是应用程序执行此操作的机会。 pInfo->m_nCurPage 成员是上次显示的页面(如果上次显示两个页面,则为左侧页面),指针是用户感兴趣的页面位置的提示。 由于框架不知道应用程序视图的结构,因此你必须提供代码才能移动到所选点。
应在调用 CView::EndPrintPreview
之前执行大多数操作。 此调用将产生与 DoPrintPreview
相反的效果并删除 pView、pDC 和 pInfo。
// Any further cleanup should be done here.
CView::EndPrintPreview(pDC, pInfo, point, pView);
CWinApp::OnFilePrintSetup
必须为“打印设置”菜单项映射此项。 在大多数情况下,不需要重写实现。
页面命名法
另一个问题是页码编号和顺序。 对于简单的字处理器类型应用程序,这是一个简单的问题。 大多数打印预览系统假定每个打印页面对应于文档中的一页。
在尝试提供通用解决方案时,需要考虑几个事项。 假定使用 CAD 系统。 用户有一个涵盖多张 E 大小纸的绘图。 在 E 大小(或更小规模)绘图仪上,页码编号非常简单。 但在激光打印机上,每张纸打印 16 个 A 大小页面,打印预览会考虑是哪一“页面”
正如引言段落所述,打印预览就像打印机一样。 因此,用户将看到所选的特定打印机会产生什么样的结果。 每页上打印什么图像可通过视图确定。
CPrintInfo
结构中的页面说明字符串提供了一种向用户显示页码的方法,前提是每页的页码可以采用一个数字表示(如“第 1 页”或“第 1-2 页”)。 CPreviewView::OnDisplayPageNumber
的默认实现使用此字符串。 如果需要其他显示,可重写此虚拟函数来提供,如“Sheet1、Sections A, B”。