How to Create Simple Reports with PrintDocument in C#
Quick Start Guide | |
---|---|
What You Will Need |
Visual Studio 2022 ComponentOne WinForms Edition ComponentOne WPF Edition |
Controls Referenced | |
Tutorial Concept | Learn how to build a report document "from scratch" in C# .NET code without requiring a complicated reporting tool. The C1PrintDocument is a more powerful C# PrintDocument component that makes it easier to dynamically build custom documents. |
Reporting is a common task in business applications, and for that, ComponentOne includes a specialized FlexReport library that allows you to make complex reports. But sometimes, using specialized tools can be too tricky or not flexible enough.
For example, if you wanted to generate a report “from scratch” by printing text to a document word by word, a reporting library would not be the best tool.
Reporting libraries are over-engineered by design to make it quick and easy. For more flexible and from-the-ground-up solutions, ComponentOne includes an extended C# PrintDocument component: C1PrintDocument.
In this blog, we'll cover topics such as:
- An Overview of C1PrintDocument
- Creating the Simple Report
- Using the Groups
- Using the Expressions
- Using the Styles
- Using the Aggregates
- Drawing the Charts
Ready to Get Started? Download ComponentOne Today!
About C1PrintDocument - An Extended C# PrintDocument Component
C1PrintDocument provides an extensive and feature-rich document object model (DOM), with support for various layouts (inline, stacked), ambient and hierarchical styles, infinitely nested tables, table of contents, detailed control over pagination (orphan/widow, page size/headers/footers, etc.), etc. On top of all that, it also has a data binding layer.
You can download the C1PrintDocument component as part of the C1.Win.Printing NuGet package (WinForms version) or C1.Xaml.WPF.PrintDocument (WPF version). It supports .NET 6 and higher with C# or VB.NET code.
Once you are familiar with it, you can relatively easily in code create pretty complex and flexible documents that then can be viewed, printed, saved, or exported in PDF (Portable Document Format), RTF (Rich Text File), MS Excel, and other formats.
If you need to print arbitrary text with pagination and different layout options, you can start using this simple code:
"Hello World" with C1PrintDocument
That's it. You can play with page settings in the preview dialog to see that your content reflows and can be paginated without any code from your side.
If you need a more complex report-like application, you need to define the layout of elements and use data binding to generate elements according to your data. To show how you can do that, we SimpleReports sample that can be downloaded as a zip file. Now let's talk about some individual samples and technics.
Create the Simple Report
The most uncomplicated "Customer Labels" sample shows labels flowing in the left to the right direction, as many labels on a single page as can fit.
To compose C1PrintDocument, you should use elements derived from the RenderObject class and add them to the C1PrintDocument.Body.
Children collection in the same way as you add the controls to control.Controls collection in WinForms application. Elements can be nested and use parent object binding as a data source.
RenderArea is a render object that is specifically a container for child objects. By default, when a new RenderArea is created, its width is equal to the width of its parent area.
For RenderArea, specifying the width or height as "Unit.Auto" means that the size of the children determines the appropriate size.
Within their container (parent object or document body), render objects by default are placed according to the stacking rules, determined by the value of the Stacking property of the container (document for top-level objects).
This value can be one of the following StackingRulesEnum enumeration members:
- BlockTopToBottom: Objects are placed one beneath the other within the container. When the bottom edge of the current page is reached, a new page is added. This is the default
- BlockLeftToRight: Objects are placed one next to another, from left to right. When the right edge of the current page is reached, a new "horizontal" page is added (a horizontal page logically extends the preceding page to the right
- InlineLeftToRight: Objects are placed inline, one next to another, from left to right. When the right edge of the current page is reached, the sequence wraps to the next line. A new page is added when the bottom of the current page is reached
The layout of the "Customer Labels" document consists of several key elements:
- The outer RenderArea object "raContainer" serves as a container for all labels.
- The RenderArea object "raItem" representing a single label.
- The RenderText object "rt" representing label content.
Here is what it should look like:
Let me show you some code with more details:
Customer Labels Code
In the above code text of each label is composed of data-bound fields using the scripting language. During document generation, the C1PrintDocument will calculate these fields and print actual values. You can read more about text expressions here.
Using the Groups
C1PrintDocument allows you to use expressions to group your data. The "Alphabetical List of Products" sample groups products by the first letter of the product name. It allows you to show a nice formatted list of products:
This sample layouts data in a table form. The table is created using the RenderTable class. The size of tables is not limited and determined at render time by the cell with the highest row and column numbers whose contents have been set.
Row and column indices start at zero. By default, the size of the table is equal to the width of the parent element's client area. Row heights are set automatically to match the largest content height in a row.
Alphabetical List of Products Code
Note: ranges of groups must not overlap.
Using the Expressions
Expressions (or scripts) are used in C1PrintDocument to extract, calculate, display, group, sort, filter, parameterize, and format the contents, and extend a report's functionality. You can read more about scripts here.
Visual Basic is used as the expression language by default. To use c# change ScriptingOptions.Language property.
The "Employees" sample uses scripts to insert photos into the generated document:
The sample uses the FormatDataBindingInstanceScript property to set a script executed each time a new instance of the current RenderObject is created due to data binding resolving.
Employees Code
Different objects in the C1PrintDocument hierarchy have properties accepting scripts that allow changing appearance on the fly depending on data. For example, you can use style to highlight orders worth $1000 or more with blue color using the .TextColorExpr expression:
Text Color by Condition
Using the Styles
The C1PrintDocument Style property is the root style of the document with which you can set the default appearance of visual components: borders, font, line spacing of a text, etc.
The C1PrintDocument PageLayout.PageSettings property helps set page options for printing to select the size of the paper, page orientation, etc.
For document pages, you can adjust settings using the Style property of the RenderObject class: content margins, page size, etc. This property cannot be assigned. Set the Parent to that other style to use another style as the base for the current object's style.
Setting Styles
Using the Aggregates
For the average report, just grouping data is not enough. People usually want to see some aggregate values at the bottom. Let's see how you can add aggregates in the "Employee Sales by Country" sample:
Employee Sales by Country Code
In the above code, the aggregate is constructed with this method call: new Aggregate("SumByCountry", "Fields!Freight.Value", tvg.DataBinding, RunningEnum.Group, AggregateFuncEnum.Sum). Parameters are:
- "SumByCountry" is the aggregate name
- "Fields!Freight.Value" is the expression for calculating the sum
- "tvg.DataBinding" is the data source for the aggregate
- "RunningEnum.Group" means that the aggregate has a group scope
- "AggregateFuncEnum.Sum" means the aggregate returns the sum of values of the expression within the scope
The C1PrintDocument.DataSchema.Aggregates.Add method adds Aggregate object to Aggregates collection. Then, the aggregate with "SumByCountry" name can be used in any place of document like this: rt.Cells[0, 2].Text = "$[Aggregates!SumByCountry.Value]".
Adding aggregate to document.Aggregates collection is not obligatory. You can use aggregate functions directly in any expression, as shown here.
Drawing the Charts
Very often, people want to see a visual representation of their data. The "Sales by Category" sample generates charts using FlexChartcontrol and then inserts images into the document.
Here is how it looks when previewed:
To use some arbitrary assembly in the C1PrintDocument scripts, it should be added to the C1PrintDocument.ScriptingOptions.ExternalAssemblies collection. To use FlexChart, we should add references to FlexChart and its dependencies.
Sales by Category Code
The Visual Basic script (used in FormatDataBindingInstanceScript in the above code) creates a chart object, assigns a data source to it, and then converts the chart to an image:
Visual Basic Script to Create the Chart
Similarly, you can add your custom assemblies to references and use your classes in document scripts.
The structure of the C1PrintDocument is hierarchical and relatively simple. The data is set using bindings and is displayed in groups that define the appearance of data.
Each element inherited from RenderObject supports the execution of scripts that extend the functionality. Our extended C# PrintDocument component is a great tool for creating simple reports or printing some unbound text with different layout options.
We will continue supporting C1PrintDocument in the .NET 4.5.2 version and in .NET 5, .NET 6, and beyond.
Ready to Get Started? Download ComponentOne Today!