5.4 使用打印框架
我们已经介绍过,可以直接使用 wxPrinterDC 来进行打印。不过,一个更灵活的方法是使用 wxWidgets 提供的打印框架来驱动打印机。要使用这个框架,最主要的任务就是要实现一个 wxPrintout 的派生类,重载其成员函数以便告诉 wxWidgets 怎样打印一页(OnPrintPage), 总共有多少页(GetPageInfo),进行页面设置(OnPreparePrinting) 等等。而 wxWidgets 框架则负责显示打印对话框,创建打印设备上下文和调用适当的 wxPrintout 的函数。同一个 wxPrintout 类将被打印和预览功能一起使用。
当要开始打印的时候,一个 wxPrintout 对象实例被传递给 wxPrinter 对象,然后将调用 Print 函数开始打印过程,并且在准备打印用户指定的那些页面前显示一个打印对话框。如下面例子中的那样。
// 一个全局变量用来存储打印相关的设置信息
wxPrintDialogData g_printDialogData;
// 用户从主菜单中选择打印命令以后
void MyFrame::OnPrint(wxCommandEvent& event)
{
wxPrinter printer(& g_printDialogData);
MyPrintout printout(wxT("My printout"));
if (!printer.Print(this, &printout, true))
{
if (wxPrinter::GetLastError() == wxPRINTER_ERROR)
wxMessageBox(wxT("There was a problem printing.\nPerhaps your current printer
is not set correctly?"), wxT("Printing"), wxOK);
else
wxMessageBox(wxT("You cancelled printing"),
wxT("Printing"), wxOK);
}
else
{
(*g_printDialogData) = printer.GetPrintDialogData();
}
}
因为打印函数在所有的页面都已经被渲染和发送到打印机以后才会返回,因此 wxPrintout 对象可以以局部变量的方式创建。
wxPrintDialogData 对象用来存储所有打印有关的数据,比如用户选择的页面和份数等。将其保存在一个全局变量中并且在下次调用的时候传递给 wxPrinter 对象是一个不错的习惯,因此象上面代码中演示的那样,在创建 wxPrinter 的时候传递给它一份全局配置数据的指针,并在 Print 函数成功返回以后将当前的打印数据保存在全局变量里(当然,一个更专业的作法是将其保存在你的应用程序类中)。关于使用打印和页面设置对话框的详情请参考第 8 章,�使用标准对话框�。
如果要创建打印预览,你需要创建一个 wxPrintPreview 对象,给这个对象传递两个 wxPrintout 对象作为参数,一个用于预览,一个则用于在用户预览的时候直接请求打印,同样的,你可以传递一个全局的 wxPrintDialogData 对象给预览对象以便预览类使用用户以前选择的打印设置数据。然后将这个预览类传递给 wxPreviewFrame,然后调用 wxPreviewFrame 的 Initialize 函数和 Show 函数显示这个窗口,如下所示:
// 用户选择打印预览菜单命令
void MyFrame::OnPreview(wxCommandEvent& event)
{
wxPrintPreview *preview = new wxPrintPreview(
new MyPrintout, new MyPrintout,
& g_printDialogData);
if (!preview->Ok())
{
delete preview;
wxMessageBox(wxT("There was a problem previewing.\nPerhaps your current printer is
not set correctly?"),
wxT("Previewing"), wxOK);
return;
}
wxPreviewFrame *frame = new wxPreviewFrame(preview, this,
wxT("Demo Print Preview"));
frame->Centre(wxBOTH);
frame->Initialize();
frame->Show(true);
}
当打印预览窗口被初始化的时候,它将禁用所有其它的顶层窗口以确保任何可能导致正在预览或者打印的文档内容发生改变的可能。关闭这个窗口将会导致两个 wxPrintout 对象自动被释放。下图显示了预览窗口的样子,可以看到它内含一个工具条,上面提供了页面遍历,打印以及缩放控制等功能。
关于 wxPrintout 的更多内容
当创建 wxPrintout 对象的时候,你可以传递一个可选的标题参数,在某些操作系统上,这个标题将显示在打印管理器上。另外,要重载一个 wxPrintout 对象,你至少需要重载 GetPageInfo, HasPage 和 OnPrintPage 函数,当然,下面介绍的这些函数你都可以视需要进行重载。
首先来介绍 GetPageInfo 函数,它用来返回最小页码,最大页码,开始打印页码和结束打印页码。前两个参数用来提供一个可以打印的范围,后两个参数被设计来反应用户选择的页码范围,不过目前没有用处。最小页面的默认值为 1,最大页码的默认值为 32000,不过当 HasPage 函数返回 False 的时候,wxPrintout 类也将停止打印。通常情况下,你的 OnPreparePrinting 应该计算当前打印内容在当前设置下的打印页数,将其保存在一个成员变量中,以便 GetPageInfo 函数返回正确的值,如下所示:
void MyPrintout::GetPageInfo(int *minPage, int *maxPage,
int *pageFrom, int *pageTo)
{
*minPage = 1; *maxPage = m_numPages;
*pageFrom = 1; *pageTo = m_numPages;
}
而 HasPage 函数则用来返回是否拥有某个页码,如果这个页码超出了最大页码的范围则必须返回 False。通常你的实现类似下面的样子:
bool MyPrintout::HasPage(int pageNum)
{
return (pageNum >= 1 && pageNum <= m_numPages);
}
OnPreparePrinting 在预览或者打印过程刚开始的时候被调用,重载它使得应用程序可以进行各种设置工作,比如计算文档的总页数等。OnPreparePrinting 可以调用 wxPrintout 的 GetDC,GetPageSizeMM,IsPreview 等函数,因此方便获取这些信息。
OnBeginDocument 是在每次每篇文档即将开始打印的时候被调用的,如果这个函数被重载了,那么必须在重载的函数中调用 wxPrintout::OnBeginDocument。同样的 wxPrintout::OnEndDocument 也必须被它的重载函数调用。
OnBeginPrinting 和 OnEndPrinting 函数则是在整个打印过程开始和结束的时候被调用,和当前打印多少份没有关系。
OnPrintPage 会被传递一个页码参数,应用程序必须重载这个函数并且在成功打印这一页以后返回 true。这个函数应该使用 wxPrintout::GetDC 来取得打印设备上下文已经进行绘画(打印)工作。
下面这些函数作为一些工具函数,你可以在你的重载函数中使用它们,而无需重载它们。
IsPreview 函数用来检测当前正处于一个预览过程中还是一个真的打印过程中。
GetDC 函数为当前正在进行的工作返回一个合适的设备上下文。如果是在真实的打印过程中,则返回一个 wxPrinterDC,如果是预览,则返回一个 wxMemoryDC,因为预览其实是在一个位图上通过内存设备上下文来渲染的。
GetPageSizeMM 以毫米为单位返回当前打印页面的大小。GetPageSizePixels 则以象素为单位返回这个值(打印机的最大分辨率)。如果是在预览过程中,这个大小和 wxDC::GetSize 返回的值通常是不一样大的(参见下面的说明),wxDC::GetSize 返回用于预览的位图的大小。
GetPPIPrinter 返回当前设备上下文每一个英寸对应的象素的数目,而 GetPPIScreen 则返回当前屏幕上每英寸对应的象素的数目。
打印和预览过程中的缩放
当你在窗口上绘画的时候,你可能不大关心图片的缩放,因为显示器的分辨率大部分都是相同的。然后,在面对一个打印机的时候,有几方面的因素可能导致你必须关心这个问题:
你需要通过缩放和重新放置图片的位置来保证图片位于某个页面之内,在某种情况下,你甚至可能需要把一个图片分成两半。
字体都是基于屏幕分辨率的,因此在打印文本的时候,你需要设置一个合适的缩放的值,以便使打印设备上下文符合屏幕的分辨率。在打印文本的时候,通过应该设置通过用 GetPPIPrinter 的值除以 GetPPIScreen 的值计算而得的值作为缩放因子的值。
当渲染预览图案的时候,wxWidgets 使用了 wxMemoryDC 在一个位图上绘画。这个位图的大小(wxDC::GetSize)是基于当前预览的放大倍数的这就需要一个额外的缩放因子。通常这个缩放因子可以通过用 GetSize 返回的值除以 GetPageSizePixels 返回的实际页面的象素值来获得。通常这个值还应该乘以别的缩放因子定义的值。
你可以调用 wxDC::SetUserScale 来设置设备上下文的缩放因子,使用 wxDC::SetDeviceOrigin 来设置平移因子(例如,需要把一个图片放置在页面正中的时候)。如果有必要的话,你甚至可以在同一个页面绘画的时候反复使用不同的值来调用这两个函数。
wxWidgets 的例子中的 samples/printing 演示了怎样在打印过程中使用缩放,下面列出的代码是其中进行缩放的适配代码,它将演示了在打印过程中和预览过程中将一幅大小为 200x200 象素的图片进行了缩放和放置,如下所示:
void MyPrintout::DrawPageOne(wxDC *dc)
{
// 下面的代码可以这样写只是因为我们知道图片的大小是 200x200
// 如果我们不知道的话,需要先计算图片的大小
float maxX = 200;
float maxY = 200;
// 让我们先设置至少 50 个设备单位的边框
float marginX = 50;
float marginY = 50;
// 将边框的大小增加到图片的周围
maxX += (2*marginX);
maxY += (2*marginY);
// 获取象素单位的当前设备上下文的大小
int w, h;
dc->GetSize(&w, &h);
//计算一个合适的缩放值
float scaleX=(float)(w/maxX);
float scaleY=(float)(h/maxY);
// 选择 X 或者 Y 方向上较小的那个
float actualScale = wxMin(scaleX,scaleY);
// 计算图片在设备上的合适位置以便居中
float posX = (float)((w - (200*actualScale))/2.0);
float posY = (float)((h - (200*actualScale))/2.0);
// 设置设备平移和缩放
dc->SetUserScale(actualScale, actualScale);
dc->SetDeviceOrigin( (long)posX, (long)posY );
// ok,现在开始画画
dc.SetBackground(*wxWHITE_BRUSH);
dc.Clear();
dc.SetFont(wxGetApp().m_testFont);
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetBrush(*wxCYAN_BRUSH);
dc.SetPen(*wxRED_PEN);
dc.DrawRectangle(0, 30, 200, 100);
dc.DrawText( wxT("Rectangle 200 by 100"), 40, 40);
dc.SetPen( wxPen(*wxBLACK,0,wxDOT_DASH) );
dc.DrawEllipse(50, 140, 100, 50);
dc.SetPen(*wxRED_PEN);
dc.DrawText( wxT("Test message: this is in 10 point text"),
10, 180);
}
在上面的例子中,我们只是简单的使用 wxDC::GetSize 来得到打印设备或者预览图像的分辨率,以便我们能够把要打印的图像放到合适的位置。我们没有关心类似每一个英寸多少个点这样的信息,因为我们不需要画精度很高的文本或者是线段。图片不需要很高的精度,因此我们只是进行简单的缩放以便它能够被合适的放置,不致于太大太小或者越界就可以了。
接下来,我们演示一下怎样进行精度很高的文本或者线段的打印以便看上去它和显示在屏幕上的是一致的。而不是只是进行简单的缩放:
void MyPrintout::DrawPageTwo(wxDC *dc)
{
// 你可以使用下面的代码来设置打印机以便其可以反应出文本在屏幕上的大小
// 另外下面的代码还将打印一个 5cm 长的线段。
// 首先获得屏幕和打印机上各自的 1 英寸的逻辑象素个数
int ppiScreenX, ppiScreenY;
GetPPIScreen(&ppiScreenX, &ppiScreenY);
int ppiPrinterX, ppiPrinterY;
GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);
// 这个缩放因子用来大概的反应屏幕到实际打印设备的一个缩放
float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);
// 现在,我们还需要考虑页面缩放
// (比如:我们正在作打印预览,用户选择了一个缩放级别)
int pageWidth, pageHeight;
int w, h;
dc->GetSize(&w, &h);
GetPageSizePixels(&pageWidth, &pageHeight);
// 如果打印设备的页面大小 pageWidth == 当前 DC 的大小,就不需要考虑这方面的缩放了
// 但是它们有可能是不一样的
// 因此,缩放吧.
float overallScale = scale * (float)(w/(float)pageWidth);
dc->SetUserScale(overallScale, overallScale);
// 现在我们来计算每个逻辑单位有多少个毫米
// 我们知道 1 英寸大概是 25.4 毫米.而 ppi
// 代表的是以英寸为单位的. 因此 1 毫米就等于 ppi/25.4 个设备单位
// 另外我们还需要再除以我们的缩放因子 scale
// (译者注:为什么这里是 scale 而不是 overallScale?)
// (其实 overallScale 比 scale 而言,多了一个预览的缩放)
// (而在预览的时候,我们是希望线段的长度按照用户设置的比例变化的)
// 因为我们的设备已经被设置了缩放因子
// 现在让我们来画一个长度为 50mm 的 L 图案
float logUnitsFactor = (float)(ppiPrinterX/(scale*25.4));
float logUnits = (float)(50*logUnitsFactor);
dc->SetPen(* wxBLACK_PEN);
dc->DrawLine(50, 250, (long)(50.0 + logUnits), 250);
dc->DrawLine(50, 250, 50, (long)(250.0 + logUnits));
dc->SetBackgroundMode(wxTRANSPARENT);
dc->SetBrush(*wxTRANSPARENT_BRUSH);
dc->SetFont(wxGetApp().m_testFont);
dc->DrawText(wxT("Some test text"), 200, 300 );
}
在类 Unix 系统上的 GTK+版本上的打印
和 Mac OS X 以及 Windows 系统不同,Unix 系统没有提供一个标准的 API 同时支持在屏幕上显示文本图片和在打印机上打印文本和图片。实际上,在类 Unix 系统中,屏幕显示是通过 X11 库(被 GTK+封装又被 wxWidgets 封装)实现的,而打印要通过发送 PostScript 命令到打印机来完成。而这两种情况下使用不同的字体都是一个麻烦。直到最近,才有很少的程序在类 Unix 上提供了所见即所得的功能。以前 wxWidgets 提供自己的 PostScript 实现,但是它很难和屏幕显示的内容完全一致。
从版本 2.8 开始,Gnome 项目组开始通过 libgnomeprint 和 libgnomeprintui 库来提供打印支持,这样大多数的打印问题才算得以解决。从 wxWidgets 的版本 2.5.4 开始,GTK+的版本通过合适的配置以后可以支持这两个库。你需要使用--with- gnomeprint 来配置 wxWidgets,这将导致 wxWidgets 在运行期自动查找 GNOME 打印库。如果找的到,就使用它完成打印,否则就使用旧的 PostScript 打印的代码。需要说明的是,这并不需要用户的机器上一定要安装 gnome 的打印库程序才可以运行,因为程序本身并不依赖这些库。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论