65.9K
CodeProject 正在变化。 阅读更多。
Home

一个可以打印任何控件的组件,包括 ListView、TreeView、DataGridView 和 Form

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (66投票s)

2006年6月12日

CPOL

8分钟阅读

viewsIcon

264721

downloadIcon

10330

终极打印组件。使用它可以打印 ListBox、ListView、TreeView、DataGridView、Form、TabPage,甚至您自己的 UserControl。

Sample Image - DemoPic.jpg

术语

在本文中,除非另有说明,以下词语被提及以指代特定含义。

  • 控件:您希望打印的控件。
  • 组件:本文正在讨论的 PrintControl 组件。

注意

此组件需要 .NET Framework 2.0 或更高版本。

引言

嗯,我想标题很清楚。这完全是关于打印的。在我之前开发一些企业应用程序时,我在应用程序中使用了许多列表,并且需要全部打印它们,但它们在不同的窗体上,所以每个窗体上,我都要创建一个 PrintDocument 组件并添加一个 PrintPage 事件处理程序来手动绘制其内容,花费数小时计算每个表的总高度和宽度,然后将其拟合到页面大小。嗯,我实际上并没有这样做。相反,我想到了一个可以自动为我完成这项工作的组件,就这样。ControlPrint 组件可以完成这项工作。

此外,我还有一些用于输入姓名、年龄、性别、地址等的输入表单。我也想打印这些信息,但必须自定义另一个打印表单,通过在打印中绘制它!!ControlPrint 组件也会将它打印到纸上。请看本文顶部的图片。

我的组件继承自 PrintDocument 组件,并在 PrintPage 事件处理程序中完成了所有打印工作。所以要使用它,请按照以下步骤操作:

  1. 实例化组件
  2. 为它提供您的控件,您很快就会知道如何操作。
  3. 调用 Print() 方法,或将其与 PrintPreviewDialog 一起使用。

您可以像对待任何 PrintDocument 组件一样对待它,使用以下代码:

PrintControl m_print = new PrintControl();
m_print.SetControl(MyControl);
prinPreviewDialog1.Document = (PrintDocument)m_print;

背景

该组件的主要思想是将您希望打印的控件,无论是 ListViewListBoxTreeView,还是其他任何控件,都绘制到打印机上,就像它在屏幕上绘制一样。有点像 WYSIWYG,或者“所见即所得”!所以,这里的主要步骤是:

  1. 获取控件。
  2. 计算显示控件内所有元素的最佳大小。纸上没有滚动条,对吧?
  3. 将控件绘制在页面上,就像在屏幕上一样。

当然,这并不那么简单,但我们会一步一步地介绍。

它是如何工作的?

ControlPrint 组件支持任何控件,包括 UserControl,它们都继承自 Control 类。它实际上调用控件的 GetPreferredSize() 方法来获取显示所有元素的控件的最佳大小。有些控件不支持此方法,并返回错误的预估大小。这就是为什么我在我的组件中添加了 CalculateSize()ApplyBestSize() 方法来为这些控件添加更多特殊支持。目前,特殊支持的控件包括:

  1. ListView
  2. TreeView
  3. Form 以及包含其他控件的控件,例如 TabPageGroupBox 等……

其他控件应该可以使用普通的 GetPreferredSize() 方法正常工作,包括 DataGridViewListBox 等。另外,Form 通常也能很好地使用 GetPreferredSize(),很少需要使用 CalculateSize() 方法。

该组件使用 Control.DrawToBitmap() 方法将其绘制到位图,然后进行打印。

您也可以使用 PrintControl.GetBitmap() 方法获取该位图,并按您喜欢的方式使用它,例如将其保存到文件,或在其他文本中打印等。

所以,在所有这些之后,打印步骤如下:

  1. 获取控件大小、停靠状态和父控件的旧值。
  2. 将停靠设置为 none,并将所有父控件的大小设置为大于所需大小,您很快就会明白原因。
  3. 更改控件的大小为首选大小,这将消除滚动条的需求。
  4. 将其绘制到位图。
  5. 恢复控件的状态,包括父控件的状态。
  6. 根据您的选择进行打印,或返回位图。

我必须移除停靠状态,因为它会阻止调整控件的大小。此外,父控件必须调整大小以适应控件的新尺寸。只有这样,才能成功将其绘制到位图。详情请参阅“有趣的点”。

重要提示

  1. 如果您自己制作控件,并希望使用 PrintControl 组件打印它,请覆盖 GetPreferredSize() 方法,并返回最适合您的控件的尺寸,如果 Control.GetPreferredSize() 方法为您的控件返回错误值。
  2. 您可以通过调用我组件中适当的大小指定方法(见下文)以指定的大小打印控件。当控件大小计算错误时,或者出于您自己的原因偏好这样做时,这可能会有帮助。
  3. 打印 TreeView 时,只会打印展开的节点,即可以通过滚动查看的节点。其他隐藏的节点将不会被打印。因此,如果您想确保打印 TreeView 中的所有节点,请使用 TreeView.ExpandAll() 方法。
  4. 当您更改控件的视觉样式时,它也会像在屏幕上一样被打印出来,很酷,不是吗?
  5. 视觉样式可能很美观,但它们会消耗更多的墨水和打印时间。所以要仔细考虑。
  6. 打印 Form 时,您可能需要移除背景颜色或图像,以使文本更清晰。
  7. 您可以将打印的位图拉伸以完全适合一页,方法是将 StretchControl 属性设置为 true。但要小心,这会破坏宽高比,并可能导致图像失真。

成员

我认为现在是时候谈谈组件的成员了。

构造函数

  • ControlPrint()

    默认构造函数,初始化组件,不带实际值。

  • ControlPrint(Control print)

    使用选定的控件初始化组件。

  • ControlPrint(Control print, bool Str)

    使用选定的控件初始化组件,并指定是否拉伸。

  • ControlPrint(Control print, int Width, int Height)

    使用选定的控件初始化组件,并指定特定的宽度和高度。

属性

  • StretchControl

    设置为 true 以将控件拉伸以填充单个打印页面。

  • PrintWidth

    要打印的控件的宽度。如果自动计算的宽度不适合控件的所有元素,您可以更改此值。

  • PrintHeight

    要打印的控件的高度。如果自动计算的高度不适合控件的所有元素,您可以更改此值。

  • ReapeatArea

    在打印多页时,要在页面之间重复打印的区域,以防止数据丢失。

方法

  • void SetControl(Control print)

    设置要打印的控件。

  • void SetControl(Control print, int Width, int Height)

    设置具有指定高度和宽度的控件。

  • Bitmap GetBitmap()

    将控件完整地绘制到位图并返回它。

  • Size CalculateSize()

    返回最适合控件的大小。

  • void ApplyBestSize()

    应用最适合控件的大小。

有趣的点

这些是我在开发组件过程中认为有趣的点。

  1. 我首先必须调整控件的大小,否则它只会打印可见部分,并且还会打印滚动条。我认为这帮助不大。
  2. 尝试在控件停靠时调整它的大小是失败的,所以我必须先解除停靠。
  3. 同样,控件的父控件必须比它大。否则,控件只会绘制可见部分。我首先尝试将父控件设为 null 然后恢复它,但这对于包含控件的 Form 不起作用。文本字段的值丢失了,我不知道为什么!!有什么想法吗?所以我决定也调整父控件的大小,然后再恢复它们。
  4. 当控件的长度大于页面的长度时,将有多页需要打印。所以我将打印的长度存储在一个私有字段中,当有多页时,我将当前页要打印的区域放在另一个位图中,然后打印第二个。请参阅代码中的 PrintPage 事件处理程序。
  5. 同样,在打印长表、列表或 DataGrid 多页时,行很可能会被分割到两页上,使其数据不可读。所以作为一个简单的解决方案,我添加了一个 RepeatArea 属性,这是一个要在下一页重新打印的区域。所以如果一行的一半在页面末尾打印,它将在下一页完整地重新打印。RepeatArea 是可修改的。如果您有更好的想法来解决这个问题,我很乐意将其添加到组件中。
  6. 如果您查看源代码,您会注意到我使用了一个递归方法来访问 TreeView 的所有节点。您从 foreach 循环中调用该方法,并在方法内部使用另一个 foreach 循环来递归调用自身。我的方法名为 EnumNodes

  7. 我使用 GetType().AssemblyQualifiedName 来标识控件的类型。此外,这还将识别继承自它的控件,因为它还将包含其基控件的名称。例如:
    if (m_ctrl.GetType().AssemblyQualifiedName.IndexOf("TreeView") >= 0)

    如果控件是 TreeView 控件或继承自它,则 if 语句将为 true

  8. 在尝试计算特殊控件(目前是 TreeViewListViewForm)的大小时,我会枚举它们的元素,分别是 NodeItemControl,获取它们的边界,然后获取最大的 Bounds.RightBounds.Bottom 作为所需的宽度和高度,当然,在添加了边距之后。

结论

我希望我已经很好地解释了如何使用该组件,并且它将对您有所帮助。我想在文章中回顾代码,但我认为它有点大且不必要。我在代码中添加了注释,也许会有帮助。如果任何人有任何想法、建议或发现任何错误,我将热切等待您的帖子。

感谢您花时间阅读本文。

© . All rights reserved.