用户控件的一体化打印实用程序






4.87/5 (24投票s)
打印用户控件的内容并提供打印设置和预览。
引言
此打印实用程序结合了两个方面的努力。它能够打印用户控件的内容,更重要的是,它是一个创建“通用”打印实用程序的尝试。后者是将通常分散在打印机属性、页面设置选项和打印预览等多个标准对话框中的功能集成到一个控件中。
背景
非常感谢 CodeProject 上 Nader Elshehabi 撰写的以下文章:A component that prints any control... 我需要打印TreeView 的内容,除其他外,上述解决方案证明做得很好。我并没有对其进行太多扩展,而是将其包装在一个打印实用程序中,该实用程序将 System.Drawing.Printing
命名空间的功能集成到 PropertyGrid 中。
另一个促使我创建此控件的动机是市面上打印对话框的多样性。如果您查看普通打印机附带的打印工具,除了例外情况,它们的外观、非标准化和惊人的复杂性往往令人不知所措、望而生畏,并且从根本上令人恐惧。它们绝对无助于人们了解他们的打印机。
这不是一个具有华丽 UI 吸引力的方案,但最初也不是我的意图。我有点专注于 System.ComponentModel
命名空间支持的 PropertyGrid 的强大功能。我相信,如果能带来附加价值,有效的设计器网格甚至可以吸引最终用户。
使用代码
解决方案包含两个项目
Codebasement.PrintControl
:打印实用程序用户控件及支持类Codebasement.PrintControl.Test
:一个简单的测试窗体
打印实用程序项目包含几个类,将在下文进行说明。
PrintDocumentControl
是实际的用户控件,如上图所示。它包含用于打印和预览操作的工具栏、用于各种设置的属性网格以及用于显示预览的 PrintPreviewControl
。顺便说一句,后者必须以编程方式使用。如果将其用作设计时拖放控件,它将无法正常工作。PrintDocumentControl
可以按原样使用,并且有一个简单的构造函数,只需要提供要打印的目标用户控件。
该控件会检查打印机假脱机服务是否正在运行,如果运行,还会检查是否安装了打印机。它将控件的各个元素连接在一起,并包含执行实际工作的 Methods,特别是 ShowPreview()
和 PrintNow()
。我可以将此功能分散到其他类中,但我决定采取简单的方法,将所有这些都包含在控件本身中。由于该控件旨在单独用于对话框中,因此项目中有一个 PrintDocumentForm
,其中已包含该控件。
ShowPreview()
方法将在下面介绍。它利用后续讨论的类,为 PrintDocumentComponent
(即 `_printDocument` 成员)分配要预览的属性。它创建一个 PreviewPrintController
(这与 PrintPreviewControl
用户控件不同),并将此控制器分配给文档。最后,它将文档交给预览用户控件。
顺便说一句,PreviewToolStrip
具有您期望的几项功能:
- (重新)显示预览
- 放大/缩小
- 适合页面
- 设置缩放因子
- 逐页浏览
- 多页视图
private void ShowPreview()
{
Cursor = Cursors.WaitCursor;
//rig up a new PrintPreviewControl
NewPrintPreviewControl();
//set several properties of the PrintDocument
_printDocument.DocumentName = _printDocumentSettings.HeaderText;
_printDocument.DefaultPageSettings = _printDocumentSettings.PageSettings;
_printDocument.PrinterSettings = _printDocumentSettings.PrinterSettings;
_printDocument.OriginAtMargins = _printDocumentSettings.OriginAtMargins;
//create a PreviewPrintController
PreviewPrintController pc = new PreviewPrintController();
pc.UseAntiAlias = true;
//assign the PreviewPrintController to the PrintDocument
_printDocument.PrintController = pc;
//assign the document to the PrintPreviewControl
_printPreviewControl.Document = _printDocument;
_printPreviewControl.StartPage = 0;
_printPreviewControl.UseAntiAlias = true;
//set page counter
_pageCounter = 1;
//set initial values for the preview toolstrip
SetPreviewToolStrip();
Cursor = Cursors.Default;
}
PrintNow()
方法与之类似。它还将 PrintDocumentSettings
中组装的 PrinterSettings
和 PageSettings
分配给 PrintDocumentComponent
。然后,使用 PrintControllerWithStatusDialog
作为之前的打印控制器,最终调用实际的 Print()
方法。
private void PrintNow()
{
DialogResult result;
if (_printDocumentSettings.PrintToFile)
{
result =
MessageBox.Show(
"The document " + _printDocument.DocumentName +
" will be saved to file " +
_printDocumentSettings.FileName +
". Continue?",
"Print to file...",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2,
0);
if (result == DialogResult.Yes)
{
PrintToFile();
return;
}
}
result =
MessageBox.Show(
"The document " + _printDocument.DocumentName +
" from page " + _printDocumentSettings.FromPage +
" to page " + _printDocumentSettings.ToPage +
" will be sent to printer " +
_printDocumentSettings.SelectedPrinter +
". Continue?",
"Print to printer...",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2,
0);
if (result == DialogResult.Yes)
{
_printDocument.PrinterSettings =
_printDocumentSettings.PrinterSettings;
_printDocumentSettings.PageSettings.PrinterSettings =
_printDocument.PrinterSettings;
_printDocument.DefaultPageSettings =
_printDocumentSettings.PageSettings;
StandardPrintController spc = new StandardPrintController();
_printDocument.PrintController =
new PrintControllerWithStatusDialog(spc,
"Printing "+_printDocument.DocumentName + " to " +
_printDocumentSettings.SelectedPrinter);
_printDocument.Print();
}
}
PrintDocumentSettings
是一个类,它提供 System.Drawing.Printing
命名空间中的一些属性。其背后的想法是通过捆绑最直接相关的內容来简化对该命名空间的访问,同时另一方面使此內容“对 PropertyGrid 可见”。这最好通过一个例子来说明:下面代码片段中的 SelectedPinter
属性会获取/设置一个成员字段,并调整一个 PrinterSettings
成员实例,其中在调用 ShowPreview()
或 PrintNow()
Methods 时收集打印机设置以供使用。
使用的 Attributes 大部分是标准的,但它们确实为使用的 PropertyGrid 提供了附加价值。我不会在这里深入讨论类型转换器的应用。例如,下面使用的 PrinterSelectionConverter
是一个包含的自定义类型转换器类,当在网格中使用时,它为属性提供组合填充,以便能够选择打印机。所以返回类型只是一个 string
,但类型转换器在后台提供了一些魔法。
[Category(catPrinter)]
[TypeConverter(typeof(PrinterSelectionConverter))]
[RefreshProperties(RefreshProperties.All)]
[Description("Choice for the selected printer.")]
[DisplayName("Selected printer")]
public string SelectedPrinter
{
get
{
return _selectedPrinter;
}
set
{
_selectedPrinter = value;
_printerSettings.PrinterName = value;
}
}
另一个例子是 PaperKind
属性。setter 将输入值解析为相关枚举的字符串值。然后,将 `_pageSettings` 成员的 PaperSize
分配给一个具有此 PaperKind 的 PaperSize 实例,否则通过自定义 SetPaperSize
Method 分配给默认值。在 getter 中,会进行内联的 PaperKind 验证,以确保该值对应于所选打印机支持的纸张尺寸集合的有效值。此集合再次由类型转换器提供。在这种情况下,它是 PaperKindSelectionConverter
。本质上是简单的代码,但通过结合程序化和声明性元素,它产生了强大的功能。
[Category(catPaper)]
[DefaultValue("A4")]
[Description("Paper size kind for selected printer.")]
[DisplayName("Paper size kind")]
[TypeConverter(typeof(PaperKindSelectionConverter))]
public string PaperKind
{
get
{
return ValidatePaperSize(_paperKind).ToString();
}
set
{
if (value != null)
{
_paperKind =
(PaperKind) Enum.Parse(typeof (PaperKind),
value.ToString());
_pageSettings.PaperSize = SetPaperSize(_paperKind);
}
}
}
文档设置还提供了打印页眉和页脚信息以及控件内容的多个选项。可以包含文本选项、页码和日期时间。还可以设置对齐、字体和颜色偏好。
PrintDocumentComponent
是来自上述文章的 PrintDocument
基类的派生类。因此,我将不再详细介绍其内容。基本要素是 CalculateSize
Methods,它提供了要打印的用户控件内容的尺寸计算。在这里,多个控件会被精确计算尺寸。否则,GetPreferredSize
是默认值。另一个关键部分是 OnPrintPage
事件处理程序。它负责按页面实际打印位图,并考虑重叠区域。可选地,它会拉伸目标控件进行打印,并将其调整为所需尺寸。我将页眉和页脚文本的打印作为一项小扩展混合了进去。
关注点
目前在 PrintDocumentComponent
类中包含的用户控件仅限于少数几个特定控件,其他控件则使用默认尺寸。目标用户控件支持 DrawToBitmap
Methods 非常重要。因此,并非所有用户控件都可以使用上述实用程序进行打印。有关此 PrintDocument
派生类的更多详细信息,请参阅上述“背景”段落中引用的原始文章(特别是相关的消息线程)。
打印特定控件的内容可能需要在 PrintDocumentComponent.CalculateSize
Methods 中添加一些内容,从而提供或计算控件内容的宽度和高度。此外,您可能需要调整展开等行为,因为内容打印最初是 WYSIWYG 视图。对于 TreeView,在 CalculateSize 中不会进行节点的展开。相比之下,在打印 PropertyGrid 的内容时,会在其中展开网格项。这说明了区别,并可以根据您的需要进行调整。此行为也可以作为可选项集成到 PrintDocumentSettings
类中。
我使用 System.Drawing.Printing
命名空间的经验是,它不是一个直接的朋友。该命名空间可以得到极大的改进,成为一个真正的面向对象模型,抽象出当前的多元化和复杂性。谁知道在 .NET 4.X 的未来或其他时候会发生什么?
已知问题和改进点
- 我尝试克隆目标用户控件以避免(可选地)调整目标控件的大小(正如我在上面使用的解决方案中那样),但这没有帮助。随意操作原始控件不是一个非常优雅的方法,所以这绝对是一个仍需改进的地方。
- 页码范围选择不起作用;即,当选择较小的范围时,打印机设置不会拾取它。我目前还不清楚为什么它不起作用。
- 根据情况,属性网格的性能可能有点慢。这里的问题是,我在几个 getter 中使用了一些内联验证,以及一个
RefreshProperties
这样的 Attribute,它非常有用,但 tends to downgrade performance(会降低性能)。 - 当前可选打印的日期格式固定为具有 ISO 标准字符月份缩写的格式。想法是它不容易被误解。当然,作为一个欧洲人,我更倾向于在日-月-年序列中找到逻辑。 ;-)
- 我为使解决方案“resharper-green”付出了一些努力,但这将取决于使用的 Resharper 版本。它在很大程度上也符合静态代码分析。
历史
- 2007 年 6 月 15 日 - 发布原始版本
- 2007 年 7 月 11 日 - 文章经过编辑并移至 CodeProject.com 的主要文章库