第二十一章 打印

分类:DotNet    发布时间:2015/6/21 23:09:46

在Windows Forms中,打印相对不难,但是这里的关键词是“相对”。他只是在你掌握了Win32打印机API的使用经验时才显得容易。打印永远不会像在屏幕上显示文本和图形那么容易。

了 解在Windows Forms中进行打印的部分困难是存在几个连锁类,这些类看上去都引用了其他类。例如,PrinterSettings类具有一个 PageSettings类型的属性,PageSettings也具有一个PrinterSettings类型的属性,而这只是刚刚开始。过一 会,System.Drawing.Printing看上去就像一个镜厅(译者注:这句话指其中的类互相引用)。了解打印的一部分过程涉及挑选出不同的 类。

21.1 打印机及其设置

Windows允许用户安装多台打印机。(更准确的说,用户可以为多台打印机安装“设备驱动程序”。打印机不必实际连接到计算机)安装打印机在Printers对话框中。

从Windows Forms程序的角度来看,一台特定的打印机是通过一个PrinterSettings类型的对象来描述的,他是在 System.Drawing.Printing名称空间中定义的。PrinterSettings只有一个默认构造函数,它用于创建一个用于默认打印机 的对象

PrinterSettings()

例如:
PrinterSettings prnset=new PrinterSettings();
//创建一个PrinterSettings的新的实例,以指向默认打印机。

PrinterSettings的属性
string PrinterName 读写
bool  IsValid  只读
bool  IsDefaultPrinter 只读

PrinterName是一个字符串,他通常表示打印机的制造商和型号。他与你在Printer对话框中看到的是同一个字符串。例如:

HP LasetJet 1100(MS)
NEC Silentwriter LC890 v47.0
Hewlett-Packard HP-GL/2 Plotter
Fax

在安装打印机期间,用户可以更改打印机的名称,因此,你在PrinterSettings对象遇到的打印机的名称可能不是标准名称。

在 创建一个新的PrinterSettings对象时,IsValid和IsDefaultPrinter属性通常被设置为true。不过,如果没有安装打 印机,那么PrinterName将返回字符串“<no default printer>”,而且IsValid=false。

请 注意,PrinterName是可写的,这意味着你可以将它设置为区别已安装的打印机的字符串。在设置PrinterName属性 时,PrinterSettings的其他所有属性都将更改为反映那台打印机。很显然,用来设置PrinterSettings的字符串必须与已经安装打 印机的名称相匹配。如果不匹配,那么也不会产生异常!但是IsValid属性将为false。

要想聪明地将PrinterName属性设置为另一台已安装的打印机的名称,可以首先使用PrinterSettings的唯一一个静态属性来获得一个已安装打印机的清单,该属性如下

PrinterSettings.StringCollection InstalledPrinters 读取

StringCollection类是在PrinterSettings内定义的。它实际上只是一个只读字符串的数组。假设你像下面这样利用InstalledPrinters属性:

PrinterSettings.StringCollection sc=PrinterSettings.InstalledPrinters();

随后,可以对sc对性爱那个使用下面所示的2个属性。数量sc.Count是已安装打印机的数量(如果没有安装任何打印机,则为0)。sc[0]是第一台打印机的名称,sc[1]是第二台打印机的名称。

PrinterSettings.StringCollection的属性
int  Count
string []

你不需要将InstalledPrinters的值保存到一个变量中。可以访问该属性本身。例如:

PrinterSettings.InstalledPrinters.Count;
PrinterSettings.InstalledPrinters[1];

你可以将PrinterName属性设置为集合中的一个字符串,从而更改PrinterSettings所指的打印机。如果已经定义了StringCollection类型的变量sc,则可以这样做:

prnset.PrinterName=sc[2];

prnset.PrinterName=PrinterSettings.InstalledPrinters[2];

除非出现严重问题,否则IsValid属性在这是应该是true,而IsDefaultPrinter将为false,即使将PrinterName设置为默认打印机的名称也是如此。

让我重复一遍:当你将PrinterName属性设置为一台已安装打印机的名称时,PrinterSettings对象的所有属性都会更改为那台打印机。

下面的几个属性表示打印机的基本功能
bool  IsPlotter 读取
bool  SupportsColor 读取
int   LandscapeAngel 读取

如 果IsPlotter属性为true,那么你可能不应该依赖于打印机来显示位图。如果打印机不支持彩色,那么你可能想要使用其他方法在一些图形中使用颜 色。(例如,如果打算为条形图或地图使用颜色,则可能想要在打印时使用抖动的画刷代替)LandscapeAngel属性通常表示90或270度。不过, 如果打印机不支持横向模式,那么该属性将等于0.

关于打印机技术,从PrinterSettings就得不到进一步的信息了(就是说,她是激光打印机、喷墨打印机还是其它类型)

PrinterSettings还具有3个返回项目集合的属性。

PrinterSettings.PaperSourceCollection PaperSources 读取 PaperSource
PrinterSettings.PaperSizeCollection PaperSizes 读取 PaperSize
PrinterSettings.PrinterResolutionCollection PrinterResolutions 读取 PrinterResolution

上面的3个属性的第一列都是在PrinterSettings类中定义的,每一种类型都有2个只读属性:Count和索引,他返回的对象具有在表的最后一列中指定的类型。

例如:
prnset.PaperSources.Count表示这些PaperSource对象的数量。
prnset.PaperSources[2]就是一个PaperSource类型的对象,她是集合中的第三个元素。如果集合中项目的数量少于3,则会产生一个异常。

让我们看一看PaperSource,PaperSize和PrinterResolution类。

PaperSource类具有下面2个只读属性
string  SourceName 读取
PaperSourceKind Kind 读取

SourceName属性是一个应该对用户有意义的文本说明(比如,“手工送纸”)。PaperSourceKind是一个枚举。

Upper 1
Lower 2
Middle 3
Manual 4
Envelope 5
ManualFeed 6
AutomaticFeed 7
。。。
Custom 257

请记住,PrinterSettings的PaperSources属性是打印机上所有可能的纸张来源的集合。该属性不表示当前默认的纸张来源。

PrinterSettings的PaperSizes是打印机支持的所有止张大小的集合。每一项都是一个PaperSize的对象。其属性

string PaperName 读写
int   Width  读写
int Height 读写
PaperKind Kind 读取

上面的PaperName属性比如“信封#10”。Width和Height属性表示纸张(或信封)大小,以百分之一英寸为单位。PaperKind是一个枚举,他的成员很多。

PaperKind枚举
Letter 1
Legal 5
Exxcutive 7
A4 9
A5 11
。。。

PrinterSettings的PrinterResolutions属性是一个PrinterResolution对象的集合。

PrinterResolution的属性
int  X 读取
int  Y 读取
PrinterResolutionKind Kind 读取

PrinterResolutionKind枚举
Custom  0
Draft  -1
Low   -2
Medium -3
High  -4

每 一台打印机在PrinterResolutions集合中至少有5项。其中4项使用值分别为Draft、Low、Medium和High的 PrinterResolutionKind,XY属性被设置为-1.这四项的值不必与特有的打印机分辨率相关联。如果打印机只能使用一种分辨率,那么所 有这些选项最后都是用同一种分辨率。

PrinterResolutions中的其余一个或多个项目表示在打印机上可用的实际设备分辨率。这些其余的项目都是用值为Custom的PrinterResolutionKind。X和Y属性表示以点每英寸为单位的实际分辨率。

例 如:一台打印机可能可以使用两种分辨率:600*600和1200*1200.PrinterResolutions集合将包含6个项目。其中两个项目使 用值为Custom的PrinterResolutionKind;一个项目使X和Y值为600;另一个项目将使X和Y值为1200.其他4个项目为 Draft、Low、Medium和High,X和Y属性为-1.

PrinterSettings的几个属性设计打印一个多页文档。

bool CanDuplex 读取
Duplex Duplex 读写
int MaximumCopies 读取
short Copies 读写
bool Collate 读写

如果打印机可以正反两面打印,那么CanDuplex将是true。如果该属性为true,那么你可以将Duplex属性设置为下面所示的值

Duplex枚举
Simplex 1
Vertical 2
Horizontal 3
Default -1

Simplex表示单面打印。Vertical和Horizontal选项是指可以进行双面打印的两种不同方式。Vertical表示页面通常是纵向装订的,就像常见的图书一样。Horizontal用于水平装订的页面。装订线通常位于顶部。

Copies属性的默认值为1,最大可以将其设置为MaximumCopies,以强制打印机驱动程序打印多份。Collate表示副本的顺序。当Collate为true时,将按照1,2,3,1,2,3的顺序打印。Collate的默认值取决于打印机。

如果以编程的方式设置下面所示的属性,则不会发生任何事情。这些属性通常与PrintDialog类一起使用

PrinterSettings属性
PrintRange PrintRange 读写
int  MinimumPage  读写
int MaximumPage 读写
int FromPage 读写
int ToPage 读写
bool PrintToFile 读写

PrinterSettings的最后一个属性是一个PageSettings类型的对象。他是System.Drawing.Printing中的另一个重要类。如下

PageSettings DefaultPageSettings 读取

PageSettings类描述了打印页面的特征。例如,PrinterSettings有一个PageSources属性,她是打印机上所有可用纸张来源的集合。PageSettings有一个PageSource属性,他表示一个特定页面的纸张来源。

顾名思义,PrinterSettings中的DefaultPageSettings属性表示默认的页面设置。正如你将看到的,你可以为整个文档更改页面设置,也可以在文档正被打印时更改每一页的设置。

PrinterSettings 具有几个方法,他们允许你与Win32代码进行交互。特别是,你可以将信息从PrinterSettings复制到DEVMODE或DEVNAMES结构 中,或者,你也可以将信息从DEVMODE或DEVNAMES结构复制到PrinterSettings。

此外,不与WIn32代码交互的 WindowsForms程序可能对PrinterSettings的一个方法感兴趣。该方法返回的内容在Win32中被称为“信息设备描述体”。你可以 使用来自CreateMeasurementGraphics的Graphics对象获得关于打印机的信息,但是不是为了在打印机页面上进行打印。这个方 法允许你在任何时候获得关于已安装打印机的额外信息。获得这种信息的能力在Windows Forms中不是很重要,主要因为与Win32 API中的字体相比,WindowsForms字体以一种更加与设备无关的方式被处理。

PrinterSettings的方法
Graphics CreateMeasurementGraphics()

现在,让我们从PrinterSettings进入PageSettings,随后我们将遇到System.Drawing.Printing的两个基本类。

21.2 页面设置

PageSettings类描述了那些可以随每一页进行更改的打印机特征。他试图在一种空白状态下考虑一个PageSettings对象。不过,一个特定的对象总是与一台特定的打印机相关联。

程序通常可以访问预先创建的PageSettings对象,比如PrinterSettings中的DefaultPageSettings属性。但是你也可以使用下面的2个构造函数创建一个PageSettings对象。

PageSettings()
PageSettings(PrinterSettings prnset)

第 一个构造函数为默认打印机创建一个PageSettings对象;第二个基于一台通过PrinterSettings参数表示的已安装的特定打印机创建一 个PageSettings对象。在任何一种情况下,PageSettings对象都包含用于那台打印机的默认页面设置。

已安装打印机的默 认页面设置是用户从Printing Preferences对话框定义的。WindowsForms程序可在打印一个文档时更改这些默认设置,但这类程序进行的任何更改都不影响其他应用程 序。例如,如果用户在Printing Preferences对话框中选择了横向模式,则WindowsForms程序能以纵向模式打印,但他不会更改Printing Preferences对话框中的横向选择。

PageSettings具有下面的8个属性,其中7个是可读又可写的。

PageSettings属性
PrinterSettings PrinterSettings 读写
bool Landscape 读写
Rectangle Bounds 读取
Margins Margins 读写
bool Color 读写
PaperSource PaperSource 读写
PaperSize PaperSize 读写
PrinterResolution PrinterResolution 读写

请 注意,表中的第一个属性是PrinterSettings,他表示与这些页面相关联的打印机。当你从PrinterSettings对象的 DefaultPageSettings属性获得一个PageSettings对象时,PageSettings对象的PrinterSettings属 性与最初的PrinterSettings对象是同一个。

换句话说,如果你创建一个名为“prnset”的PrinterSettings对象,则

(prnset==prnset.DefaultPageSettings.PrinterSettings)

返回true。请记住,这些对象都是引用,因此,你在prnset中对属性作出的任何更改都将在prnset.DefaultPageSettings.PrinterSettings中反映出来。

不过,如果你创建一个名为“pageset”的PageSettings,则

(pageset== pageset.PrinterSettings.DefaultPageSettings)

返回false,即使两个对象的所有相应属性最初都是相等的,情况也是如此。一个PageSettings类型的对象就是一个特定页面的设置。你可能想要更改特定页面的设置,而不是更改文档的默认页面设置。

在大多数情况下,你将使用PageSettings中的其余属性直接获得信息。不过,你的程序还可以(在一定限度内)设置用来更改页面打印方式的属性。

例如,Landscape属性为false表示纵向模式而true表示横向模式。这些信息是很充分的。你的应用程序可以根据页面的方向使用这些信息以稍微不同的方式打印。但是,你的程序还可以更改属性本身,而不需要用户进行任何干预。

只 读的Bounds属性是一个Rectangle对象,他表示以百分之一英寸为单位的页面大小,采用这种单位是考虑了纸张大小和Landscape设置。例 如,信封尺寸的止张在纵向模式下的Bounds属性是(0,0,850,1100),在横向模式下,Bounds属性为(0,0,1100,850)

Margins属性表示页面的默认边框,他最初在4个边上都被设置为1英寸。你可以使用下面所示的构造函数构造一个新的Margins对象。

Margins()
Margins(int Left,int Right,int Top,int Bottom)

Margins属性
int Left 读写
int Right 读写
int Top 读写
int Bottom 读写
//单位是百分之一英寸

有时,即使打印机支持彩色打印,用户也会指定不应该以彩色方式打印页面。可能因为喷墨打印机的彩色墨盒是空的。PageSettings对象的Color属性表示用户是否想要在页面上使用颜色。

接 下来的3个属性是PaperSource、PaperSize和PrinterResolution。你将回忆起PrinterSettings类具有3 个属性:PaperSources、PaperSizes和PrinterResolutions(他们都是复数),这些属性与PageSettings 的三个属性相对应。例如,PageSettings中的PaperSource属性是一个来自PrinterSettings中的 PaperSources集合中的项目。

如果你想要从程序中更改这3个属性中的一个,则一定要从相应集合的一个成员设置该属性。例如,如果有一个名为“pageset”的PageSettings类型的对象,并且你想要将打印机的分辨率更改为Draft,那么代码可能如下:

foreach (PrinterResolution prnres in pageset.PrinterSettings.PrinterResolutions)
{
 if (prnres.Kind==PrinterResolutionKind.Draft)
    pageset.PrinterResolution=prnres;
}

foreach 语句在PrinterResolutions集合的所有项目之间循环,该集合位于与PageSettings对象相关联的PrinterSettings 对象中。当存在匹配时,代码将设置PrinterResolution属性。你需要从预先创建的PrinterResolution对象来设置 PageSettings的PrinterResolution属性,因为PrinterResolution类没有公共的构造函数。

你的 程序想要更改PaperSource或PaperSize属性的情况并不多见。不过,假设你实现了一项邮件合并功能,并且想在一个打印作业中以不同方式打 印信件和信封。你需要相应地更改PaperSource和PaperSize的属性,更改的依据是用户在应用程序中作出的决定。

PaperSize 不受Landscape属性的影响。如果Landscape属性为false,那么Bounds的Width和Height属性将等于PaperSize 的Width和Height属性。如果Landscape属性为true,则Bounds的Width和Height属性将被交换。PaperSize的 属性不会交换。

迄今为止,我还没有介绍实际打印一些内容的知识。这项工作需要定义一个PrintDocument类型的对象。

21.3 定义文档

打印作业由在一台特定的打印机上打印的一页或多页组成,她是通过PrintDocument类表示的。PrintDocument只有一个构造函数

PrintDocument()

通常,通过创建一个PrintDocument类型的对象开始打印作业:

PrintDocument prndoc=new PrintDocument();

你 可以为每一个打印作业创建一个新对象。不过,如果正在使用标准的打印对话框--或者允许用户选择打印机和打印机选项的其他方法,那么你可能想要保留 PrintDocument中的这些设置,并为程序的持续时间使用同一个实例。在这种情况下,你可以将prndoc定义为一个字段,并且只创建他一次。

PrintDocument具有下面4个属性,但是其中两个是PrinterSettings和PageSettings类型的对象,因此,第一眼看到PrintDocument时,你会发现其中的信息比自己期望的要多得多。

PrintDocument属性
PrinterSettings PrinterSettings 读写
PageSettings  DefaultPageSettings 读写
string  DocumentName 读写
PrintController PrintController 读写

在创建一个新的PrintDocument对象时,PrinterSettings属性表示默认打印机。如果需要,可以更改PrinterSettings属性或者PrinterSettings的单个属性。例如:

prndoc.PrinterSettings.Copies=2;

DefaultPageSettings属性最初是从PrinterSettings对象的DefaultPageSettings属性设置的。你也可以更改它,例如更改那个属性的属性,如下所示:

prndoc.DefaultPageSettings.Landscape=true;

对于一个新的PrintDocument对象,表达式

prndoc.PrinterSettings == prndoc.DefaultPageSettings.PrinterSettings

返回true,但是

prndoc.DefaultPageSettings==prndoc.PrinterSettings.DefaultPageSettings

返回false。这是因为你可能想要更改文档的DefaultPageSettings,而不更改打印机的默认页面设置。

DocumentName属性被初始化为文本字符串"document"。你可能想要更改这个值,无论什么时候打印作业被识别,比如,排列于窗口中的那些正在打印而又未完成的打印作业,这个名称就会显示出来。

稍后讨论PrintController属性

PrintDocument类具有下面的4个公共事件:
BeginPrint OnBeginPrint PrintEventHandler PrintEventArgs

QueryPageSettings OnQueryPageSettings QueryPageSettingsEventHandler QueryPageSettingsEventArgs

PrintPage OnPrintPage PrintPageEventHandler PrintPageEventArgs

EndPrint OnEndPrint PrintEventHandler PrintEventArgs

对于每一个打印作业,BeginPrint和EndPrint事件只被触发一次。QueryPageSettings和PrintPage事件是打印作业中的每一个页面触发的。PrintPage事件处理程序表示是否存在多个要打印的页面。

最 起码,你需要为PrintPage事件设置一个事件处理程序。如果想要为每一页使用不同的页面(例如,在一个单独的打印作业中改变信件和信封的打印),则 需要为QueryPageSettings事件安装一个处理程序。如果需要长时间执行初始化或清除操作,则应该为BeginPrint和EndPrint 安装事件处理程序。

最后通过下面的这个方法初始化打印

void Print()

这是 PrintDocument中唯一一个与事件不相关的方法。在程序完成打印之前,Print方法不会返回。在这段时间里,程序不响应任何用户输入。期间, 程序安装的PrintDocument事件处理程序将被调用,从BeginPrint处理程序开始,然后是每一页的QueryPageSettings和 PrintPage,最后是EndPrint处理程序。

21.4 处理PrintDocument的事件

下面的类层次结构展示了与PrintDocument事件处理程序有关的EventArgs的各个派生类。

Object=》EventArgs=》

1)PrintPageEventArgs
2)CancelEventArgs=》PrintEventArgs=》QueryPageSettingsEventArgs

CancelEventArgs是在System.ComponentModel名称空间中定义的。与BeginPrint和EndPrint事件相关联的PrintEventArgs对象具有一个从CancelEventArgs继承而来的单个属性。

bool Cancel 读写

BeginPrint事件处理程序将Cancel设置为true,以放弃打印作业。(例如,当打印作业需要的内存比可用内存更多时)

QueryPageSettingsEventArgs类向Cancel添加了另一个属性

QueryPageSettings的属性
PageSettings  PageSettings 读写

QueryPageSettings事件的处理程序可以更改PageSettings属性,以便为相应的PrintPage事件做准备。

PrintPageEventArgs 类具有下面的4个只读属性以及2个读写属性

PrintPageEventArgs的属性
Graphics Graphics 读取
bool  HasMorePages 读写
bool Cancel 读写
PageSettings PageSettings 读取
Rectangle PageBounds 读取
Rectangle MarginBounds 读取

Graphics 对象对每一个页面都是重新创建的。如果为一页设置Graphics对象的属性--比如PageUnit或PageScale,则不要期望这些属性对后面的 页面同样有效。默认的PageUnit是GraphicsUnit.Display,他使打印机看上去像100dpi的设备。Graphics对象的 DpiX和DpiY属性反映了PageSettings的PrinterResolution属性。

在打印页面事件处理程序的入口处,HasMorePages总是被设置为false。为了打印多页,必须在从事件处理程序返回时,为再次调用的事件处理程序将该属性设置为true。在最后一页,使该属性为false。

Cancel属性通常也被设置为false。如果你的程序需要放弃打印作业,则应该将其设置为true。在那个操作系统中,不将HasMorePages设置为true会终止已经 位于队列中正在打印的页面,将Cancel属性设置为false与此不同。

PageSettings属性用于在打印时提供信息。该属性将反映在QueryPageSettings事件处理程序中所做的任何更改。

为 了让你方便,PrintPageEventArgs对象还包括了一个PageBounds矩形,他与PageSettings的Bounds属性相同,还 包括了一个MarginBounds矩形,她是页面大小减去PageSettings的Margins属性所表示的页边距。

让我们先看一些代码。首先,这里有一个简单的对话框,它允许用户从一个组合框中挑选一台已安装的打印机:

例子961页

让 我们先看一看MenuFilePrintOnClick方法。这个方法在用户从File菜单中选择Print时执行。他通过创建一个新的 PrintDocument对象和一个新的PrinterSelectionDialog对象开始。PrinterSelectionDialog中的构 造函数使用已安装打印机填充一个组合框。

随后,该方法将对话框的PrinterName属性设置为默认打印机:

dlg.PrinterName=prndoc.PrinterSettings.PrinterName;

那台打印机将成为组合框中被选定的项目。

如果用户通过按下OK从对话框返回,则PrintDocument对象的PrinterSettings属性的PrinterName属性将被设置为选定的打印机:

prndoc.PrinterSettings.PrinterName=dlg.PrinterName

随 后,该方法使用与我在前面完成这样的工作所使用的相似代码将PrintDocument的DefaultPageSettings属性的 PrinterResolution属性设置为草稿模式。现在,文档的所有页面都将使用与草稿模式相关联的分辨率进行打印。(你可以通过在 PrintPage方法中查看Graphics对象的DpiX和DpiY属性来确定这一分辨率)

最 后,MenuFilePrintOnClick方法设置PrintDocument对象DocumentName属性,为PrintPage和 QueryPageSettings事件安装处理程序,初始化页面,并且调用PrintDocument中的Print方法,以开始打印。

请注意,除了使用Graphics对象的当前的VisibleClipBounds属性居中文本之外,PrintPage不需要做任何特殊事情。VisibleClipBounds反映了打印机当前的打印方向。

无 论你合适从PrintThreePages进行打印,PrintDocument对象都是新创建的。这意味着你的默认打印机总是做为对话框中选定的打印机 显示出来,即使你切换到前一个打印作业中的另一台打印机也是如此。你可能在考虑将PrintDocument对象存储为一个字段。

21.5 页面大小

要想智能地在打印页面上打印文本和图形,你需要知道一些关于可打印区域大小的细节。我一直在假设你可以在打印机页面上的任何可打印区域进行打印。但是,实际上你只能在用户指定的页边空白之内打印。

不幸的是,在WindowsForms应用程序的打印方面,考虑用户选择的页边距是有问题的。你可以认为自己掌握了需要的所有信息,但是实际情况并不是这样。

让 我们看看你掌握了哪些信息。一个PrintPage事件处理程序接受了一个被传入的PrintPageEventArgs类型的对象。该类的一个属性是名 为“PageBounds”的Rectangle对象。PageBounds等于PageSettings类的Bounds属性,并且他表示以百分之一英 寸为单位的物理页面的大小,同时考虑到了纵向和横向。例如,对于纵向的8.5英寸*11英寸的信封大小,PageBounds等于 (0,0,850,1100)

PageSettings类还包括了一个名为“Margins”的对象,他表示用户希望的是在页面4个边上以百分之一英寸为单位的页边空白。在默认情况下,所有4个页边距都被初始化为100.

PrintPageEventArgs的MarginBounds属性是一个基于PageBounds的Rectangle对象,但是考虑了页边距。对于具有默认页边距的信封大小的纸张,MarginBounds是矩形(100,100,650,900).

迄 今为止,一切都很好。不过,问题是您从PrintPageEventArgs获得的Graphics对象北设置为在页面的“可打印区域”上进行打印。打印 机通常不能在非常接近纸张边缘的地方进行打印,因为在打印机上存在着滚筒、进纸器和其他一些古怪的部件。这个Graphics对象的原点---也就是当你 在绘制方法中指定(0,0)点时图形所出现的位置--是页面可打印区域的左上角。该原点与Graphics对象的VisibleClipBounds属性 一致。

当我的打印机使用8.5英寸 * 11 英寸的止张并且被设置为纵向打印时,VisibleClipBounds报告一个坐标为(0,0,800,1060)的矩形。在默认情况下,这些数值的单 位是百分之一英寸,因此页面的可打印区域为8英寸宽,10.6英寸高。不可打印区域在左右两侧一共是0.5英寸,在上下一共是0.4英寸。

不过,你不能假设我的打印机上的不可打印区域在左右两侧都是0.25英寸,在上下都是0.20英寸。根据不同的打印机,不可打印区域在页面的左右和上下可能不是平均分配的。

你 所需要的是一个相对整个页面描述了页面可打印区域的矩形。不幸的是,我们已经用尽了信息。在PrinterSettings、PageSettings、 PrintDocument或PrintPageEventArgs中不存在更多揭示如何沿着页面边缘分配不可打印区域的信息了。

如果你满 足于使用估计值,则可以相对于在用户选定的区域内描述页面区域的VisibleClipBounds来计算一个矩形(从而可以与Graphics的绘制方 法一起使用)。如果PrintPageEventArgs对象被命名为“ppea”,并且Graphics对象被命名为“grfx”,则表达式

(ppea.PageBounds.Width-grfx.VisibleClipBounds.Width) /2

就是页面左侧的不可打印区域的近似值,而

(ppea.PageBounds.Height-grfx.VisibleClipBounds.Height) /2

是页面顶部的不可打印区域的近似值。从ppea.MarginBounds.Left和ppea.MarginBounds.Top中分别减去这些值,你就会获得绘制坐标中的点,他近似等于符合用户给出页边距的页面区域的左上角。

下面这个关于显示矩形的计算考虑到了用户的页边距:

RectangleF rectf=new RectangleF(
 ppea.MarginBounds.Left-
   (ppea.PageBounds.Width-grfx.VisibleClipBounds.Width)/2,
 ppea.MarginBounds.Top-
   (ppea.PageBounds.Height-grfx.VisibleClipBounds.Height)/2,
 ppea.MarginBounds.Width,ppea.MarginBounds.Height);

让我再强调一次,这是一种近似计算,因为他假设在左右和上下平均分配不可打印的页边空白.但是,这是你能够在WindowsForms界面中做的最理想的事情了.

例子 967页

如果你对这种近似不满意,则必须使用PHYSICALOFFSETX和PHYSICALOFFSETY参数来访问Win32函数GetDeviceCaps.

如果你更喜欢使用百分之一以外的单位,则可以使用PrinterUnitConvert类将PageBounds和MarginBounds值转换为其他单位.这个类具有一个Convert静态方法.他有6个版本.

PrinterUnitConvert类的Convert静态方法

int Convert(int iValue,PrinterUnit puFrom,PrinterUnit puTo)
double Convert(double dValue,PrinterUnit puFrom,PrinterUnit puTo)
Point Convert(Point pt,PrinterUnit puFrom,PrinterUnit puTo)
Size Convert(Size size,PrinterUnit puFrom,PrinterUnit puTo)
Rectangle Convert(Rectangle rect,PrinterUnit puFrom,PrinterUnit puTo)
Margins Convert(Margins margins,PrinterUnit puFrom,PrinterUnit puTo)

上表中的PrinterUnit是一个枚举.
Display   0 //表示百分之一英寸
ThousandthsOfAnInch 1
HundredthsOfMillimeter 2
TenthsOfAMillimeter 3

21.6 打印控制器

在前面讨论PrintDocument类时,我跳过了PrintController属性.在默认情况下,你可以将该属性设置为一个从PrintController抽象类派生而来的类的实例.

下面显示了类的层次结构

Object=>PrintController=>
1)StandardPrintController
2)PrintControllerWithStatusDialog
3)PreviewPrintController

PrintController类定义了下面4个方法
void OnStartPrint(PrintDocument prndoc,PrintEventArgs pea)
Graphics OnStartPage(PrintDocument prndoc,PrintPageEventArgs ppea)
void OnEndPage(PrintDocument prndoc,PrintPageEventArgs ppea)
void OnEndPrint(PrintDocument prndoc,PrintEventArgs pea)

正 如你已经看到的,当一个程序通过调用PrintDocument类的Print方法开始打印时,PrintDocument对象将通过触发该类所定义的4 个事件来响应.这些事件是BeginPrint、QueryPageSettings、PrintPage和EndPrint。

但 是,PrintDocument还调用通过其PrintController属性所表示的特定PrintController对象的4个方法。 PrintDocument在触发他自己的BeginPrint事件之后调用PrintController的OnStartPrint方法。 PrintDocument在触发每一个PrintPage事件之前和之后调用OnStartPage和OnEndPage。最 后,PrintDocument在出法他自己的EndPrint事件之后调用PrintController中的OnEndPrint。

特别是,PrintController中的OnStartPage方法负责获得最后被传递给PrintPage事件处理程序的Graphics对象。(请注意从OnStartPage方法返回的值。)这个对象实质上确定了PrintPage中的图形输出的去向。

当 然,图形输出通常流向打印机,而且这是PrintController对象的责任。不过,PreviewPrintController还想做其他一些事 情。这个特定的控制器基于一副展示打印机页面的位图创建了一个Graphics对象。而这就是在WindowsForms中实现打印预览的方式。

PrintDocument的默认PrintController属性是一个PrintControllerWithStatusDialog类型的对象,这个名称准确地揭示了打印机控制器的另一项责任:它显示一个对话框,其中展示了打印文档和正在打印的页面的名称。

如 果不希望显示该对话框,则可以将PrintDocument的PrintController属性设置为一个 StandardPrintController类型的对象。StandardPrintController几乎具有 PrintControllerWithStatusDialog的所有功能,只是不显示对话框。

若喜欢使用对话框以外的方式显示打印过程,则可以从StandardPrintController派生一个类,例如,下面有一个在状态栏窗格中显示列打印状态的打印控制器:

例子970页

21.7 使用标准的打印对话框

PrintDialog是System.Window.Forms中的通用对话框集合的一部分,他是一个允许用户选择打印机并更改打印机设置的对话框。PrintDialog还为用户包括了一项功能,指定打印整个文档、某一范围的页面,还是一块选定内容。

你可以创建一个如下所示的新的带有默认构造函数的PrintDialog对象。

PrintDialog();

你还必须初始化下面所示属性的一个(但不是两个)

PrintDialog属性
PrintDocument Document 读写
PrinterSettings PrinterSettings 读写

设置Document属性是首选的;随后,PrintDialog对象使用来自PrintDocument对象的PrinterSettings属性来设置他自己的PrinterSettings属性。

PrintDialog 附带的一些额外选项包括允许用户打印整个文档、某一范围的页面或者当前选定的内容。PrintDialog对话框将这些选项(标记为“All”、 “Pages”和“Selection”)显示为单选按钮。在默认情况下只启用了All选项,当然,她是被选定的。

你还可以选择启用Pages和Selection按钮。可以使用下面所示的属性达到这个目的(并选择对话框上的其他几个选项)。

PrintDialog的属性
                           默认值
bool  AllowSelection 读写  false
bool  AllowSomePages 读写  false
bool  AllowPrintToFile 读写 true
bool  PrintToFile   读写   false
bool  ShowNetwork   读写   true
bool  ShowHelp     读写    false

如果将ShowHelp设置为true,则必须为HelpRequest事件(从CommonDialog继承而来)安装一个处理程序。AllowPrintToFile属性启用打印到文件的复选框。PrintToFile表示是否选定了复选框。

当 你启用Pages单选按钮时,用户就可以键入起始页码和终止页码。你可以为这两个字段指定初始值和最小值及最大值,但不作为PrintDialog中的属 性。相反,这些属性是被定义在PrinterSettings中的。在设置了PrintDialog的Document属性之后,你可以使用 PrintDialog的PrinterSettings属性以引用下面的属性。

PrinterSettings的属性
PrintRange PrintRange 读写
int   MinimumPage  读写
int  MaximumPage   读写
int  FromPage      读写
int  ToPage       读写
bool PrintToFile  读写

PrintRange属性是一个PrintRange类型的枚举。
AllPages  0
Selection 1
SomePages 2

你 可能想要将MinimumPage设置为1,将MaximumPage设置为文档的页面总数。你还可以将FromPage和ToPage设置为相同的值。 对于某些应用程序(比如Notepad模拟程序)而言,这实际上不是一种好方法。在显示PrintDialog对话框时,用户有机会更改打印机‘方向和纸 张大小等等,还可以更改其中任何可能改变打印文档总页数的项目。

像任何通用对话框一样,在初始化PrintDialog对象之后,你可以调 用他的ShowDialog方法。该方法返回一个DialogResult枚举值。在从PrintDialog返回时,PrintRange属性表示用户 已经选择了哪些选项。对于某一范围内的页面,FromPage和ToPage表示页面范围。

例子975页

21.8 设置页面

与打印相关联的第二个通用对话框是PageSetupDialog。这个对话框通常让用户指定页边距、页面方向、纸张来源和纸张大小。但是,该对话框还可以用于选择默认打印机和打印机选项。PageSetupDialog只有一个构造函数

PageSetupDialog构造函数
PageSetupDialog()

随后你可以(只可以)设置下面所示属性中的一个
PrintDocument Document 读写
PrinterSettings PrinterSettings 读写
PageSettings PageSettings 读写

设 置Document属性是推荐的做法。随后,PageSettings设置来自PrintDocument对象的PrinterSettings和 PageSettings属性。要想使每一件事情都正常进行,必须使用同时带有PageSetupDialog和PrintDialog的 PrintDocument对象。

下面列出了其余的PageSetupDialog属性。
bool AllowMargins 读写
bool AllowOrientation 读写
bool AllowPaper 读写
bool AllowPrinter 读写
bool ShowNetwork 读写
bool ShowHelp 读写
Margins MinMargins 读写

几乎所以bool的属性在默认情况下都是true,但ShowHelp例外。将他们设置为false会禁用对话框某些方面的功能。Network按钮位于在按下Printer时出现的另一个对话框中。MinMargins属性在默认情况下被设置为全是0.

用户在PageSetupDialog对话框中做出的更改反映在对话框从PrintDocument对象获得的PageSettings对象中。

例子 978页

21.9 打印预览

一 旦应用程序支持打印,实现一个打印预览特性就相当容易。基本上,你的常规PrintPage事件处理程序用于在位图表面上,而不是在打印机上显示打印机输 出。随后,这些位图展示给用户。但是,在我告诉您这有多容易之前,让我们看一看幕后发生了哪些事。如果我喜欢采用不同的方法处理打印预览位图,则可能想要 知道这些细节。

打印预览的关键在于PrintDocument的PrintController属性。在默认情况 下,PrintController被设置为PrintControllerWithStatusDialog,但是我已经向您展示了如何该属性更改为其 他值,以显示状态对话框的另一种形式。

为了获得一种更极端的效果,你可以将PrintDocument的PrintController属性设置为一个PreviewPrintController类型的对象:

PreviewPrintController ppc=new PreviewPrintController();
prndoc.PrintController=ppc;

PreviewPrintController类只有一个属性。如下所示
bool UseAntiAlias 读写

将PrintDocument对象的其他属性设置为常规值,就像你打算进行打印一样。随后,通过调用PrintDocument的Print方法开始正常的打印:
prndoc.Print()

回 忆一下,打印控制器负责获得PrintDocument传递给PrintPage事件处理程序的Graphics对象。 PreviewPrintController不会获得一个用于打印机的Graphics对象。相反,他为每一页创建一副位图,并且获得一个 Graphics对象,以便在那副位图上进行绘制。这实际上是传递给PrintPage事件处理程序的Graphics对象。

在Print方法返回时,你可以访问这些位图。PreviewPrintController的唯一一个不是继承而来的方法返回一个PreviewPageInfo类的数组。如下所示
PreviewPageInfo[] GetPreviewPageInfo();

PreviewPageInfo类具有两个属性,如下所示
Image Image 读取
Size PhysicalSize 读取

上 面的Image属性的像素大小是打印机页面的像素大小。PhysicalSize属性表示以百分之一英寸为单位的大小。现在,你有了一个位图的集合,每一 幅位图都与打印文档的一页相对应。你可按照自己的想法显示这些位图。存在更简单的方法吗?是的,存在。你可以在开始时创建一个 PrintPreviewDialog类型的对象:

PrintPreviewDialog predlg=new PrintPreviewDialog();

PrintPreviewDialog是从Form派生而来的,因此他具有很多属性、方法和事件。但是,你不必担心其中的多数属性、方法和事件。下面列出了PrintPreviewDialog自己实现的一些属性。

PrintDocument Document 读写
PrintPreviewControl PrintPreviewControl 读取
bool UseAntiAlias  读写
bool HelpButton    读写

必 须设置的重要属性是Document,应该将其设置为用于打印和页面设置的同一个PrintDocument对象。 PrintPreviewControl是另一个在System.Windows.Forms中定义的类,他描绘了使用您的页面图像显示位图的控件,这些 控件最后将出现在窗体上。

下面是初始化和开始打印预览的常规代码:

predlg.Document=prndoc;
predlg.ShowDialog();

ShowDialog 方法完成所有工作。他采用存储为他的Document属性的PrintDocument对象,将PrintController设置为 PreviewPrintController,调用PrintDocument的Print方法,然后显示一个带有一系列控件的窗体,在窗体中显示了那 些位图。你还可以从打印预览中进行打印,在这种情况下,打印预览对话框对相同的事件处理程序使用同一个PrintDocument对象。对于这种可能性, 你应该在调用ShowDialog之前设置PrintDocument的DocumentName属性。

让我们在Notepad模拟程序中使用页面设置和打印预览实现打印。

例子983页

最新评论

我要发表评论

名称:
电子邮件:
个人主页:
内容:

 博客分类