轻松将 ListView 转换为精美的打印报告,并附带打印预览






4.94/5 (73投票s)
ListViewPrinter 类可以接收现有的 ListView,并轻松将其转换为漂亮的报告。

引言
您崭新的应用程序已经完美完成——而且还提前了一周。在向管理层演示后,CEO 说:“太棒了。我喜欢。但我希望所有这些信息都能作为一份报告。我希望能够随意调整报告中的列,随意移动和调整它们的大小。它必须是可排序、可分组的,就像我们在屏幕上看到的那样。当然,在打印之前,我们必须能够进行打印预览。明天能完成吗?”你短暂地考虑过是否要提及 PrintScreen 键,但你怀疑他不会对那个解决方案感到兴奋。
管理层喜欢报告。程序员却不那么喜欢。报告通常是需求的最后考虑。然而,生成精美的报告并非易事。如果你有暴雪的预算并且有几个月的时间来弄清楚如何使用它们,你总是可以购买商业产品。至少有一个不错的开源 .NET 报表解决方案,但学习曲线仍然很长。
对我这样的懒惰而虚荣的程序员来说,我真正想要的是一个实施起来毫不费力,但却能产生惊人效果的东西。我想在同一天回到我的 CEO 那里,向他展示他想要的漂亮报告,然后提醒他我应该得到的加薪。
ListViewPrinter
的设计目标正是如此。
ListViewPrinter 解决方案
ListViewPrinter
的目的是很简单:它将一个 ListView
转换成一个漂亮的报告。生成的报告可以进行打印预览,调整页面设置,以及(当然)打印。
一如既往,ListViewPrinter
的一个主要设计目标是让它能够快速轻松地使用。因此,通常情况下,程序员会在 IDE 中创建并配置 ListViewPrinter
实例。然后,它的使用方式如下:
this.listViewPrinter1.PrintPreview();
就是这样!在 IDE 中,你可以指定要打印哪个列表视图,以及设置许多格式选项。只需一行代码,你就可以生成类似这样的内容:
![]() |
这不是列表视图的屏幕截图。这是报告的打印预览,它处理了分页和缩小以适应——这份报告你可以打印出来交给你的 CEO,最终获得你应得的加薪。 从示例中可以看到,报告有六个部分,每个部分都可以自定义:
所有这些部分(水印除外)都可以:
笔刷可以是渐变、纹理或纹理笔刷,所有这些结合起来可以让你制作出你想要的壮观报告——或者做得太过分,就像你在演示中看到的。 |
控制格式
输出的格式主要在 IDE 中完成。ListViewPrinter
的每个实例都公开以下属性:HeaderFormat
、FooterFormat
、ListHeaderFormat
、GroupHeaderFormat
和 CellFormat
。这些可以展开以修改各个方面。
这很好,但有其局限性。在 IDE 中,没有标准的 Pen
或 Brush
对象编辑器。对于 Brush
,最多只能公开一个 Color
并将其转换为 SolidBrush
。对于 Pen
,最多只能指定宽度和颜色。如果你想使用更花哨的 Brush
或 Pen
(为了获得最佳报告效果,你应该这样做),你就必须编写一些代码。
![]() |
选择格式时,你应该记住报告是围绕块组织的:页面页眉和页脚、组和列表页眉以及列表单元格都是块。在一个块内,你可以指定:
在块的每一侧,你可以指定:
图表显示了顶部和底部侧的这些设置。它们同样可以设置在块的左侧和右侧。 |
格式灵活性的很大一部分来自 Brush
类的多功能性。有几种 Brushes
:
SolidBrush
(如图所示),其中区域被绘制成单一颜色(可以是半透明的)。HatchBrush
,其中区域被绘制成图案。TextureBrush
,其中区域被绘制成图像。LinearGradientBrush
,其中区域被绘制成逐渐变化的颜色(如上面的页眉和页脚所示)。
花时间理解各种 Brush
类是值得的。
言归正传
配置完成后,ListViewPrinter
的主要编程接口很简单。命令直接对应于主要的打印命令:
void PageSetup();
void PrintPreview();
void PrintWithDialog();
其思想是将每个命令链接到相应的菜单事件处理程序,如下所示:
private void pageSetupToolStripMenuItem_Click(object sender,
EventArgs e) {
this.listViewPrinter1.PageSetup();
}
private void printPreviewToolStripMenuItem_Click(object sender, EventArgs e) {
this.listViewPrinter1.PrintPreview();
}
private void printToolStripMenuItem_Click(object sender, EventArgs e) {
this.listViewPrinter1.PrintWithDialog();
}
要进行无需用户交互的打印,你可以调用:
this.listViewPrinter1.Print();
有趣的代码片段
在编写这样一个项目时,总会有很多有趣的细节出现。
容错空间
PrintPageEventArgs.MarginBounds
中提供的边距并不可靠。好吧,技术上说是可靠的,但它们的意思并非你所想的。在打印预览时它们工作正常,但当你打印真实页面时,打印输出总是感觉有点偏离中心。而且这种偏离中心的程度因打印机而异。
问题在于页面的不可达区域。几乎总有一些区域是打印机无法触及的,而这些区域对于不同的打印机来说是不同的。.NET 2.0 将这些不可打印的区域表示为硬边距。
因此,为了计算打印输出的有效边距,代码需要如下所示:
if (this.PrintController.IsPreview)
this.pageBounds = (RectangleF)e.MarginBounds;
else
this.pageBounds = new RectangleF(e.MarginBounds.X -
e.PageSettings.HardMarginX,
e.MarginBounds.Y - e.PageSettings.HardMarginY, e.MarginBounds.Width,
e.MarginBounds.Height);
状态化执行
打印时需要注意的一点是,每个页面是单独打印的。当框架需要时,你的代码会被调用来打印下一页,然后再次退出。这意味着在每页的末尾,打印代码必须存储足够的状态信息,以便在需要下一页时能够恢复。如果你不习惯用有限状态机编程,这种状态化可能需要一点时间来适应。
打印水印
我不确定是否有人实际使用水印,但实现起来很有趣,所以它作为一项功能(不是推荐的项目范围确定方法)。水印问题有两个部分:如何旋转字符串,以及如何半透明地打印文本。
旋转文本需要一个不错的 DrawRotatedString()
方法或修改矩阵变换。.NET 没有 DrawRotatedString()
方法,所以我们只能依赖变换。这实际上是一件好事,因为变换功能更强大。一般变换定义了如何将点从一个坐标空间映射到另一个坐标空间。如果这似乎没有多大帮助,那么你只需要知道它们可以用来产生旋转、缩放和倾斜等效果。 .NET 有一个不错的矩阵类,它提供了创建各种变换矩阵的实用方法。一旦我们有了想要的变换矩阵,我们只需将其提供给我们的 Graphics
对象,之后的所有绘图都将被变换。
所以,为了绘制我们旋转的文本,我们创建一个旋转矩阵,然后将其应用于 Graphic
对象。
Matrix m = new Matrix();
m.RotateAt(watermarkRotation, Utils.CalculateCenter(this.pageBounds));
g.Transform = m;
半透明地打印文本仅仅是创建一个带有半透明颜色的笔刷,然后用该笔刷绘制文本。
int alpha = (int)(255.0f * (float)this.WatermarkTransparency /
100.0f);
Brush brush = new SolidBrush(Color.FromArgb(alpha,
this.WatermarkColorOrDefault));
g.DrawString(this.Watermark, this.WatermarkFontOrDefault, brush,
this.pageBounds,
strFormat);
当完成字符串绘制后,我们清除变换,以便绘图恢复正常。
g.ResetTransform();
限制
正如你可能从名称中猜到的,ListViewPrinter
只与 ListViews
一起工作。如果你不使用 ListView
来呈现数据,那么 ListViewPrinter
将无法帮助你。你必须拿出你的存折,然后取消你几周的社交生活。但是许多应用程序确实使用 ListViews
,而这个类就是为了帮助它们而设计的。
ListView
必须处于 Details
视图模式,ListViewPrinter
才能知道如何打印它。如果它处于任何其他视图模式,报告将为空白。
ListViewPrinter
无法打印处于虚拟模式的 ListView
。这是因为在虚拟模式下,你无法使用 Items
集合。因此,无法获取 ListView
中的第 n 个 ListViewItem
。但请参阅下面的“无耻推广”,了解绕过此限制的方法。
ListViewPrinter
不是,也不旨在成为通用的报表解决方案。它没有提供运行总计、放置图形、绘制三角形或显示垂直文本的机制。它只是打印列表视图。
无耻推广
ListViewPrinter
可以很好地处理普通的 ListViews
,但与 ObjectListViews
结合使用效果更好。例如,当与 ObjectListView
结合使用时,子项可以有图像,所有自定义渲染器都可以正常工作,甚至虚拟列表视图也可以打印。对于懒惰和虚荣的程序员来说,ObjectListView
是一个很大的帮助。 有关 ObjectListView 的详细信息,请参见此处。
待办事项
允许在 IDE 中直接编辑 Pens 和 Brushes。
结论
希望这段代码能帮助你打印出基于 ListView 的数据,并最终获得你应得的加薪。
历史
2007 年 11 月 29 日 - 版本 1.2
- 列表行现在会换行而不是省略文本。你可以通过
CellFormat.CanWrap = false;
关闭此行为。感谢 dgortemaker 的建议。 - 处理
ListViewItem
的子项少于列表视图中列数的情况(感谢 Bernd Melchert)
2007 年 11 月 11 日 - 版本 1.0