使用 Inka 进行面向对象打印,第一部分





4.00/5 (4投票s)
Inka (一款开源打印组件) 的基础知识
引言
这是关于 Inka 系列文章的第一部分。Inka 是一个用于 .NET 平台的开源报表和打印引擎。本部分将介绍如何开始使用 Inka,以及如何使用自定义对象准备和打印基本报表。
背景
大多数报表引擎都遵循面向数据库的打印方法。通常,您会打印从数据库中获取、排序、格式化、分组或以其他方式处理以便更好地使用的数据。在 .NET 世界中,您可以使用来自数据集的数据,但基本思想是相同的。您获取一个关系数据库(或其快照),然后将数据直接移至表示层。虽然从拖放的角度来看这可能很方便,但这种方法会产生几个不愉快的副作用。
- 数据库不仅仅是持久化工具,而是成为系统不可或缺的一部分。
- 虽然您的应用程序设计(希望如此)是面向对象的,但您的表示层是面向数据库的,因此您无法抽象出持久化实现的细节。
- 在表示过程中涉及的任何业务逻辑都应该被转换为 SQL 或在表示层中实现。
- 即使报表引擎可以选择显示业务对象,但由于系统最初是作为以数据库为中心的引擎设计的,因此需要额外的努力来适应您的对象。例如,可能需要一个额外的“外键”属性,或者您的类必须实现某个特定的接口。
这对于 Microsoft Access 报表,甚至对于 .NET 业余爱好者来说可能是可以接受的,但对于任何严肃的项目来说,违反这些核心设计原则都是不可行的。由于我找不到任何开源的打印项目,所以我决定自己编写一个。
Inka 不同。Inka 不关心您从哪里获取数据:数据库、Web 服务或手动创建。她关心的是您的领域结构:对象、属性、集合和关系。
您可以在 SourceForge 网站上 下载 Inka。
入门
要打印您的数据
- 以编程方式准备布局。您可以创建一个
Inka.Report
对象,或者(更好的是)继承此类。 - 为各个节分配一个或多个数据源。
- 使用准备好的
Report
对象创建一个新的Inka.WinForms.ReportPrinter
对象。 - 调用其
Print
方法。
让我们打印一个 HelloWorld
字符串。
创建报表
每个报表应该有一个或多个节,每个节有一个或多个元素,包括其他节。因此,为了显示一个 string
(对应于一个 LabelElement
),我们必须至少创建一个节,将其添加到我们的报表中,然后向其中添加一个标签。
Public Class HelloReport
Inherits Report
Dim mainSection As New Section
'It makes sense to create the report structure in the constructor
Public Sub New()
mainSection.AddElement(New LabelElement With {.Text = "Hello world"})
Me.Sections.Add(mainSection)
End Sub
End Class
显然,这个报表不需要任何数据源。
打印报表
将 Print
方法添加到 Report
类中,或者像大多数教程推荐的那样继承 System.Drawing.Printing.PrintDocument
,这似乎很诱人。但是,这会引入不必要的依赖关系,同时使其更难测试和扩展。我真正想要的是让 Report
类仅仅是一个包含节、服务和其他重要数据的结构。换句话说,它应该是被动的。
因此,我添加了另一个类,其唯一职责是打印报表。打印报表所需的最少代码如下:
Dim report As New HelloReport
Dim printer As New Inka.WinForms.ReportPrinter(report)
printer.Print()
您可以考虑将 Print
方法作为扩展方法添加到 Report
类中。
添加一些数据
现在,让我们修改一下需求。假设文本应该显示“Hello name”,其中 name 应在运行时设置。实现此目的最直接的方法是将容器节的 DataSource
属性设置为一个适当的对象,并使用 DataElement
来显示数据。让我们更详细地回顾这些步骤。
最显而易见的方法是将 DataSource
属性设置为包含我们需要的名称的 string
。这种方法适用于其他对象,但对于 string
却不起作用。为什么?请记住,String
是 IEnumerable
,因此布局引擎将生成多个节。例如,如果名称是“Bob”,我们将有三个节,分别包含“Hello B”、“Hello o”、“Hello b”。因此,我们有两种选择:要么将名称封装在一个自定义对象中,要么创建一个包含一个元素的字符串数组。
DataElement
是所有显示数据绑定文本的元素的基类。其 DataObject
属性是用于计算实际打印文本的对象。Text
属性用作格式字符串。因此,假设我们有一个对象,其 Name
属性是我们需要的名称,那么我们将 Text
属性设置为“Hello [Name]”。请注意,属性名称应放在方括号中。您还可以在单个元素中使用多个字段,例如“Hello [Name] [LastName]”。
但是,如果我们决定使用字符串数组作为数据源,我们应该将 Text
设置为“Hello []”。这里的方括号不带字段名称,表示我们应该使用对象本身(更准确地说,是其 ToString
方法),而不是其属性。
将所有内容放在一起,我们得到:
Public Class HelloReport
Inherits Report
Dim mainSection As New Section
Public Sub New()
mainSection.AddElement(New DataElement With {.Text = "Hello [Name]"})
Me.Sections.Add(mainSection)
End Sub
Sub SetData(ByVal data As Object)
mainSection.DataSource = data
End Sub
End Class
用法
Dim report As New HelloReport
report.SetData(New With {.Name = "Bob"})
Dim printer As New Inka.WinForms.ReportPrinter(report)
printer.Print()
最后,让我们看一个更实际的示例,其中包含多个数据“行”。我们将使用自定义对象的列表作为数据源:
Dim dataSource() As Object = {New With {.Name = "Bob"}, _
New With {.Name = "Fyodor"}, _
New With {.Name = "Abdullah"}}
report.SetData(dataSource)
我们不对报表源进行任何修改。但是,当我们打印报表时,我们会注意到行会重叠。因此,我们为节设置 Size
属性:
Dim mainSection As New Section With _
{.Size = New Utils.Rectangle(0, 20), _
.KeepTogether = Section.KeepTogetherType.Detail}
另一个参数会影响节的大小计算方式。请注意,宽度对于节来说并不重要。在未来的版本中,高度将自动计算(并可以手动调整)。
下载中包含的示例代码包含显示我们自定义对象的报表,以及预览它所需的代码。我决定为您节省纸张,因此实际的打印代码被注释掉了。
结论
我们已经了解了报表引擎最基本的操作:显示静态文本和数据行。在下一部分中,我将向您展示如何实现不同类型的分组、添加聚合函数以及解决分页问题。