Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Object-oriented Printing with Inka, Part 1

4.00/5 (4 votes)
27 Feb 2009LGPL35 min read 1   396  
The basics of Inka, an open source printing component

Introduction

This is the first part of a series of articles about Inka, an open source reporting and printing engine for the .NET platform. This part is about getting started with Inka, and preparing and printing a basic report using your custom objects.

Background

Most reporting engines follow the database-oriented approach to printing. Typically, you print data that is fetched from a database, sorted, formatted, grouped, or shaped in some other way in order to become more usable. In the .NET world, you can use data coming from a dataset, but the basic idea is the same. You take a relational database (or its snapshot), and you move your data straight to the presentation layer. Although it might seem convenient from the dran-n-drop perspective, this approach has several unpleasant side effects:

  • Instead of being just a persistence tool, the database becomes an integral part of the system.
  • While your application design is (hopefully) object-oriented, your presentation is database-oriented, so you can't abstract away from the persistence implementation details.
  • Any business logic that is involved in the presentation process should be either transformed into SQL or implemented in the presentation layer.
  • Even when a report engine has an option to display business objects, the design of the system, being initially engineered as a database-centric engine, requires an extra effort to adapt your objects. For example, it might be required to have an extra «foreing key» property, or your classes have to implement a certain interface.

This might be an acceptable way of doing things for a Microsoft Access report, or even for a .NET hobbyist, but violating such core design principles is not an option for any serious project. As I couldn't find any open source project for printing, I decided to write my own.

Inka is different. Inka doesn't care where you get your data from: a database, a Web service, or created manually. What she cares about is your domain structure: objects, properties, collections, and relationships.

You can download Inka from the SourceForge site.

Getting Started

To print your data:

  1. Prepare the layout programmatically. You can create an Inka.Report object or (better) inherit from this class.
  2. Assign one or several data sources to the sections.
  3. Create a new Inka.WinForms.ReportPrinter object using the prepared Report object.
  4. Call its Print method.

Let's print a HelloWorld string.

Creating a Report

Each report should have one or more sections, each having one or more elements, including other sections. So, in order to display a string, which corresponds to a LabelElement, we have to create at least one section, add it to our report, and add a label to it:

VB.NET
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

Obviously, this report doesn't need any data sources.

Printing the Report

It would be tempting to just add a Print method to the Report class, or even inherit from System.Drawing.Printing.PrintDocument, as it is recommended in most tutorials. However, it would introduce an unnecessary dependency, at the same time making it harder to test and extend. What I really wanted is to make the Report class merely a structure that holds sections, services, and other vital data. In other words, it should be passive.

So, I added another class whose sole responsibility is printing reports. The code required to print a report is this:

VB.NET
Dim report As New HelloReport
Dim printer As New Inka.WinForms.ReportPrinter(report)
printer.Print()

You might consider adding a Print method to the Report class as an extension method.

Adding Some Data

Now, let's modify our requirements. Suppose the text should say «Hello name», where name should be set at runtime. The most straightforward way of achieving this is to set the DataSource property of the container section to an appropriate object, and use a DataElement to display the data. Let's review these steps in more detail.

The most obvious way would be to set the DataSource property to the string that contains the name we need. This approach works with other objects, but fails with strings. Why? Remember that String is IEnumerable, so the layout engine will produce several sections. For example, if the name is «Bob», we'll have three sections containing «Hello B», «Hello o», «Hello b». So, we have two choices: either encapsulate the name in a custom object, or create a string array of one element.

The DataElement is the base class for all elements displaying data bound text. Its DataObject property is the object used in calculating the actually printed text. The Text property serves as the formatting string. So, suppose we have an object whose Name property is the name we need, then we should set the Text property of our element to «Hello [Name]». Note that the property name should be put in square brackets. You can also use several fields in a single element, like «Hello [Name] [LastName]».

If, however, we decided to use a string array for the data source, we should have set the Text to «Hello []». Here the square brackets without a field name indicate that we should use the object itself (more precisely, its ToString method) rather than its property.

Putting it all together, we have:

VB.NET
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

Usage:

VB.NET
Dim report As New HelloReport
report.SetData(New With {.Name = "Bob"})
Dim printer As New Inka.WinForms.ReportPrinter(report)
printer.Print()

Finally, let's see a more realistic example, in which we have several «rows» of data. We'll be using a list of custom objects as our data source:

VB.NET
Dim dataSource() As Object = {New With {.Name = "Bob"}, _
   New With {.Name = "Fyodor"}, _
   New With {.Name = "Abdullah"}}
report.SetData(dataSource)

We don't make any modifications to the report source. However, when we print our report, we notice that the rows are superimposed. So, we set the Size property for the section:

VB.NET
Dim mainSection As New Section With _
   {.Size = New Utils.Rectangle(0, 20), _
   .KeepTogether = Section.KeepTogetherType.Detail} 

The other parameter influences the way the size of the section is calculated. Note that the width is not relevant for a section. In future releases, the height will be calculated automatically (with a possibility of manual adjustment).

The sample code included in the download contains the report displaying our custom objects, and the code required to preview it. I decided to save you some paper, so the actual printing code is commented out.

Conclusion

We have seen the most basic operations one expects from a reporting engine: displaying static text and data rows. In the next part, I'll show you how to implement different kinds of grouping, add aggregate functions, and solve paging issues.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)