ComponentOne Reports for WinForms Designer Edition: ComponentOne Reports for WinForms > Reports for WinForms Top Tips > C1PrintDocument Tips

C1PrintDocument Tips

The following tips relate to the C1PrintDocument component.

Tip 1: Setting RenderTable Width and Auto-sizing

When setting up RenderTable objects it is important to take into account that by default, the width of a RenderTable is set to the width of its containing object – which usually defaults to the width of the page. This sometimes leads to unexpected results, in particular if you want the table's columns to auto-size; to do so the table's Width must be explicitly set to Auto (which for a table means the sum of the columns' widths).

For example, the following code builds a document with an auto-sized table:

•      Visual Basic

Dim doc As New C1PrintDocument()

 

' Create a RenderTable object:

Dim rt As New RenderTable()

 

' Adjust table's properties so that columns are auto-sized:

' 1) By default, table width is set to parent (page) width,

' for auto-sizing we must change it to auto (so based on content):

rt.Width = Unit.Auto

' 2) Set ColumnSizingMode to Auto (default means Fixed for columns):

rt.ColumnSizingMode = TableSizingModeEnum.Auto

' That's it, now the table's columns will be auto-sized.

 

' Turn table grid lines on to better see auto-sizing, add some padding:

rt.Style.GridLines.All = LineDef.[Default]

rt.CellStyle.Padding.All = "2mm"

 

' Add the table to the document

doc.Body.Children.Add(rt)

 

' Add some data

rt.Cells(0, 0).Text = "aaa"

rt.Cells(0, 1).Text = "bbbbbbbbbb"

rt.Cells(0, 2).Text = "cccccc"

rt.Cells(1, 0).Text = "aaa aaa aaa"

rt.Cells(1, 1).Text = "bbbbb"

rt.Cells(1, 2).Text = "cccccc cccccc"

rt.Cells(2, 2).Text = "zzzzzzzzzzzzzzz zz z"

•      C#

C1PrintDocument doc = new C1PrintDocument();

 

// Create a RenderTable object:

 RenderTable rt = new RenderTable();

 

// adjust table's properties so that columns are auto-sized:

// 1) By default, table width is set to parent (page) width,

// for auto-sizing you must change it to auto (so based on content):

rt.Width = Unit.Auto;

// 2) Set ColumnSizingMode to Auto (default means Fixed for columns):

rt.ColumnSizingMode = TableSizingModeEnum.Auto;

// That's it, now the table's columns will be auto-sized.

 

// Turn table grid lines on to better see auto-sizing, add some padding:

rt.Style.GridLines.All = LineDef.Default;

rt.CellStyle.Padding.All = "2mm";

 

// Add the table to the document

doc.Body.Children.Add(rt);

 

// Add some data

rt.Cells[0, 0].Text = "aaa";

rt.Cells[0, 1].Text = "bbbbbbbbbb";

rt.Cells[0, 2].Text = "cccccc";

rt.Cells[1, 0].Text = "aaa aaa aaa";

rt.Cells[1, 1].Text = "bbbbb";

rt.Cells[1, 2].Text = "cccccc cccccc";

rt.Cells[2, 2].Text = "zzzzzzzzzzzzzzz zz z";

For a complete example see the AutoSizeTable sample installed with ComponentOne Reports for WinForms.

Tip 2: Using Parent/Ambient Parent Styles to Optimize Memory Usage

When rendered, paragraphs and other C1PrintDocument objects have styles that can be modified "inline". For example, like this:

•      Visual Basic

Dim RenderText rt As New RenderText("testing...")

rt.Style.TextColor = Color.Red

•      C#

RenderText rt = new RenderText("testing...");

rt.Style.TextColor = Color.Red;

This is fine for small documents or styles used just once in the whole document. For large documents and styles used throughout the document, it is much better to use parent styles using the following pattern:

1.   Identify the styles you will need. For instance, if you are building a code pretty printing application, you may need the following styles:

•      Default code style

•      Language keyword style

•      Comments style

2.   Add a child style to the document's root Style for each of the styles identified in step 1, like this for example:

•      Visual Basic

Dim doc As New C1PrintDocument()

' Add and set up default code style:

Dim sDefault As Style = doc.Style.Children.Add()

sDefault.FontName = "Courier New"

sDefault.FontSize = 10

' Add and set up keyword style:

Dim sKeyword As Style = doc.Style.Children.Add()

sKeyword.FontName = "Courier New"

sKeyword.FontSize = 10

sKeyword.TextColor = Color.Blue

' Add and set up comments style:

Dim sComment As Style = doc.Style.Children.Add()

sComment.FontName = "Courier New"

sComment.FontSize = 10

sComment.FontItalic = True

sComment.TextColor = Color.Green

•      C#

C1PrintDocument doc = new C1PrintDocument();

// Add and set up default code style:

Style sDefault = doc.Style.Children.Add();

sDefault.FontName = "Courier New";

sDefault.FontSize = 10;

// Add and set up keyword style:

Style sKeyword = doc.Style.Children.Add();

sKeyword.FontName = "Courier New";

sKeyword.FontSize = 10;

sKeyword.TextColor = Color.Blue;

// Add and set up comments style:

Style sComment = doc.Style.Children.Add();

sComment.FontName = "Courier New";

sComment.FontSize = 10;

sComment.FontItalic = true;

sComment.TextColor = Color.Green;

3.   In your code, whenever you create a C1PrintDocument element representing a part of source code you're pretty printing, assign the corresponding style to the element style's Parent, for example:

•      Visual Basic

Dim codeLine As New RenderParagraph()

MessageBox.Show("Hello World!")

' say hi to the world

Dim p1 As New ParagraphText("MessageBox")

p1.Style.AmbientParent = sKeyword

codeLine.Content.Add(p1)

Dim p2 As New ParagraphText(".Show(""Hello World!""); ")

p2.Style.AmbientParent = sDefault

codeLine.Content.Add(p2)

Dim p3 As New ParagraphText("// say hi to the world")

p3.Style.AmbientParent = sComment

codeLine.Content.Add(p3)

doc.Body.Children.Add(codeLine)

•      C#

RenderParagraph codeLine = new RenderParagraph();

MessageBox.Show("Hello World!"); // say hi to the world

ParagraphText p1 = new ParagraphText("MessageBox");

p1.Style.AmbientParent = sKeyword;

codeLine.Content.Add(p1);

ParagraphText p2 = new ParagraphText(".Show(\"Hello World!\"); ");

p2.Style.AmbientParent = sDefault;

codeLine.Content.Add(p2);

ParagraphText p3 = new ParagraphText("// say hi to the world");

p3.Style.AmbientParent = sComment;

codeLine.Content.Add(p3);

doc.Body.Children.Add(codeLine);

That's it, you're done. If you consistently assign your predefined styles to AmbientParent (or Parent, see below) properties of various document elements, your code will be more memory efficient (and more easily manageable).

You may have noted that you assigned your predefined styles to the AmbientParent property of the elements' styles. Remember, in C1PrintDocument styles, ambient properties affect content of elements, and by default propagate via elements' hierarchies – so nested objects inherit ambient style properties from their parents (unless a style's AmbientParent property is explicitly set). In contrast to that, non-ambient properties affect elements' "decorations" and propagate via styles' own hierarchy determined by styles' parents – so for a non-ambient style property to affect a child object, its style's Parent property must be set.

The usefulness of this distinction is best demonstrated by an example: suppose you have a RenderArea containing a number of RenderText objects. To draw a border around the whole render area you would set the area's Style.Borders. Because Borders is a non-ambient property, it will draw the border around the area but will not propagate to the nested text objects and will not draw borders around each text – which is normally what you'd want. On the other hand, to set the font used to draw all texts within the area, you again would set the area's Style.Font. Because Font, unlike Borders, is an ambient property it will propagate to all nested text objects and affect them – again usually achieving the desired result. So when you are not setting styles' parent/ambient parent properties – things normally "just work". But when you do use styles' parents – you must take the distinction between ambient and non-ambient style properties into consideration.

Note that for cases when you want to affect both ambient and non-ambient properties of an object, you may use the Style.Parents (note the plural) property – it sets both Parent and AmbientParent properties on a style to the specified value.

Tip 3: Using Expressions to Customize Page Headers

Sometimes it is necessary to use a different page header for the first or last page of a document. While C1PrintDocument provides a special feature for that (see the PageLayouts – note the plural – property), for cases when the difference between the header on the first and subsequent pages is only in the header text, using an expression may be the best approach. For instance if you want to print "First page" as the first page's header and "Page x of y" for other pages, the following code may be used:

•      Visual Basic

Dim doc As New C1PrintDocument()

doc.PageLayout.PageHeader = New RenderText("[iif(PageNo=1, ""First page"", ""Page "" & PageNo & "" of "" & PageCount)]")

•      C#

C1PrintDocument doc = new C1PrintDocument();

doc.PageLayout.PageHeader = new RenderText(

  "[iif(PageNo=1, \"First page\", \"Page \" & PageNo & \" of \" & PageCount)]");

In the string representing the expression in the code above, the whole expression is enclosed in square brackets – they indicate to the document rendering engine that what is inside should be treated as an expression (those are adjustable via TagOpenParen and TagCloseParen properties on the document).

Whatever is inside those brackets should represent a valid expression in the current C1PrintDocument's script/expression language. By default it is VB.NET (but can be changed to C# – see below), hence you use a VB.NET iif function to adjust your page header text depending on the page number. Here's the expression that is actually seen/executed by the document engine:

iif(PageNo=1, "First page", "Page " & PageNo & " of " & PageCount)

Because you must specify this expression as a C# or VB.NET string when assigning it to the page header text, you have to escape double quotes. Variables PageNo and PageCount are provided by the document engine (for a complete list of special variables accessible in different contexts in expressions, see the Expressions, Scripts, Tags topic).

As was mentioned, the default expression/script language used by C1PrintDocument is VB.NET. But C# can also be used as the expression language. For that, the C1PrintDocuments Language property must be set to C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp. Using C# as the expression language, our example would look like this:

•      Visual Basic

Dim doc As New C1PrintDocument()

doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp

doc.PageLayout.PageHeader = New RenderText("[PageNo==1 ? ""First page"" : ""Page "" + PageNo + "" of "" + PageCount]")

•      C#

C1PrintDocument doc = new C1PrintDocument();

doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp;

doc.PageLayout.PageHeader = new RenderText(

  "[PageNo==1 ? \"First page\" : \"Page \" + PageNo + \" of \" + PageCount]");

There were two changes:

•      Instead of VB.NET iif function, the C# conditional operator (:?) was used.

•      Instead of VB.NET's string concatenation operator (&), C#'s (+) was used.

Note that expressions are real .NET language expressions, and all normally accessible features of the corresponding language may be used in expressions. For instance instead of string concatenation you could have used the string.Format method as follows:

•      Visual Basic

doc.PageLayout.PageHeader = New RenderText("[iif(PageNo=1, ""First page"", " & "string.Format(""Page {0} of {1}"", PageNo, PageCount))]")

•      C#

doc.PageLayout.PageHeader = new RenderText("[iif(PageNo=1, \"First page\", " + "string.Format(\"Page {0} of {1}\", PageNo, PageCount))]");

Tip 4: Data Binding and Expressions

Databound render objects together with expressions are among the less known but extremely powerful C1PrintDocument features. In this example, you'll build a document with a render table data bound to a list of objects in memory. The list elements will represent Customer records with just two (for brevity) fields – Name and Balance:

•      Visual Basic

Public Class Customer

    Private _name As String

    Private _balance As Integer

    Public Sub New(ByVal name As String, ByVal balance As Integer)

        _name = name

        _balance = balance

    End Sub

    Public ReadOnly Property Name() As String

        Get

            Return _name

        End Get

    End Property

    Public ReadOnly Property Balance() As Integer

        Get

            Return _balance

        End Get

    End Property

End Class

•      C#

public class Customer

{

  private string _name;

  private int _balance;

  public Customer(string name, int balance)

  {

    _name = name;

    _balance = balance;

  }

  public string Name { get { return _name; } }

  public int Balance { get { return _balance; } }

}

The following code can be used to create a list of customer records and fill it with some sample data:

•      Visual Basic

' build sample list of customers

Dim customers As New List(Of Customer)()

Dim rnd As New Random(DateTime.Now.Second)

For i As Integer = 0 To 599

    customers.Add(New Customer("Customer_" & (i + 1).ToString(), rnd.[Next](-1000, 1000)))

Next

•      C#

// build sample list of customers

List<Customer> customers = new List<Customer>();

Random rnd = new Random(DateTime.Now.Second);

  for (int i = 0; i < 600; i++)

    customers.Add(new Customer("Customer_" + (i+1).ToString(), rnd.Next(-1000, 1000)));

Note that the Balance field's value ranges from -1000 to 1000 – so the field allows negative values. This will be used to demonstrate a new C1PrintDocument feature, style expressions, below.

The following code prints the list created above as a RenderTable in a C1PrintDocument:

•      Visual Basic

Dim doc As New C1PrintDocument()

Dim rt As New RenderTable()

' Define data binding on table rows:

rt.RowGroups(0, 1).DataBinding.DataSource = customers

' Bind column 0 to Name:

rt.Cells(0, 0).Text = "[Fields!Name.Value]"

' Bind column 1 to Balance:

rt.Cells(0, 1).Text = "[Fields(""Balance"").Value]"

' Add the table to the document:

doc.Body.Children.Add(rt)

•      C#

C1PrintDocument doc = new C1PrintDocument();

RenderTable rt = new RenderTable();

// Define data binding on table rows:

rt.RowGroups[0, 1].DataBinding.DataSource = customers;

// Bind column 0 to Name:

rt.Cells[0, 0].Text = "[Fields!Name.Value]";

// Bind column 1 to Balance:

rt.Cells[0, 1].Text = "[Fields(\"Balance\").Value]";

// Add the table to the document:

doc.Body.Children.Add(rt);

Databinding is achieved with just 3 lines of code. The first line defines a row group on the table, starting at row 0 and including just that one row:

•      Visual Basic

' Define data binding on table rows:

rt.RowGroups(0, 1).DataBinding.DataSource = customers)

•      C#

// Define data binding on table rows:

rt.RowGroups[0, 1].DataBinding.DataSource = customers;

The other two lines show two syntactically different but equivalent ways of binding a table cell to a data field:

•      Visual Basic

' Bind column 0 to Name:

rt.Cells(0, 0).Text = "[Fields!Name.Value]"

' Bind column 1 to Balance:

rt.Cells(0, 1).Text = "[Fields(""Balance"").Value]"

•      C#

// Bind column 0 to Name:

rt.Cells[0, 0].Text = "[Fields!Name.Value]";

// Bind column 1 to Balance:

rt.Cells[0, 1].Text = "[Fields(\"Balance\").Value]";

As noted, the "Fields!Name"  notation is just syntactic sugar for referencing the element called Name in the Fields array, and allows to avoid the need to use escaped double quotes.

Now, remember that the Balance field in the sample data set can be positive or negative. The following line will make all negative Balance values appear red colored in the document:

•      Visual Basic

rt.Cells(0, 1).Style.TextColorExpr = "iif(Fields!Balance.Value < 0, Color.Red, Color.Blue)"

•      C#

rt.Cells[0, 1].Style.TextColorExpr = "iif(Fields!Balance.Value < 0, Color.Red, Color.Blue)";

This demonstrates a new C1PrintDocument feature – style expressions. Starting with 2009 v3 release, all style properties have matching expression properties (ending in "Expr"), which allow you to define an expression that would be used at run time to calculate the effective corresponding style property. While this feature is independent of data binding, it can be especially useful in data bound documents as shown here.

Style expressions allow the use of predefined C1PrintDocument tags related to pagination. For instance, the following code may be used to print a render object ro on red background if it appears on page with number greater than 10 and on green background otherwise:

•      Visual Basic

ro.Style.BackColorExpr = "[iif(PageNo > 10, Color.Red, Color.Green)]"

•      C#

ro.Style.BackColorExpr = "[iif(PageNo > 10, Color.Red, Color.Green)]";

Finally, it should be noted that while VB.NET is the default expression language in C1PrintDocument, C# can be used instead if the Language property is set on the document:

•      Visual Basic

doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp

•      C#

doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp;

With this in mind, our current sample may be rewritten as follows:

•      Visual Basic

Dim doc As New C1PrintDocument()

doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp

Dim rt As New RenderTable()

' Define data binding on table rows:

rt.RowGroups(0, 1).DataBinding.DataSource = customers

' Bind column 0 to Name:

rt.Cells(0, 0).Text = " [Fields[""Name""].Value]"

' Bind column 1 to Balance:

rt.Cells(0, 1).Text = " [Fields[""Balance""].Value]"

rt.Cells(0, 1).Style.TextColorExpr = "(int)(Fields[""Balance""].Value) < 0 ? Color.Red : Color.Blue"

' Add the table to the document:

doc.Body.Children.Add(rt)

•      C#

C1PrintDocument doc = new C1PrintDocument();

doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp;

RenderTable rt = new RenderTable();

// Define data binding on table rows:

rt.RowGroups[0, 1].DataBinding.DataSource = customers;

// Bind column 0 to Name:

rt.Cells[0, 0].Text = " [Fields[\"Name\"].Value]";

// Bind column 1 to Balance:

rt.Cells[0, 1].Text = " [Fields[\"Balance\"].Value]";

rt.Cells[0, 1].Style.TextColorExpr =

  "(int)(Fields[\"Balance\"].Value) < 0 ? Color.Red : Color.Blue";

// Add the table to the document:

doc.Body.Children.Add(rt);

Note the following as compared to code that used VB.NET as expressions/scripting language:

•      The use of square brackets as C1PrintDocument's open/close tag parentheses (adjustable via TagOpenParen and TagCloseParen properties) does not conflict with their C# use for array indexing within the expressions because after seeing the first opening bracket, C1PrintDocument tries to find a matching closing one.

•      Because a field's Value property is of type object, we need to cast it to an int for the conditional expression to work correctly (VB.NET does that automatically).

•      The "Fields!Name" notation cannot be used as it is purely a VB.NET feature.

Tip 5: Customizing Databound Table Columns

In C1PrintDocument's tables, you can have data bound columns rather than rows. Consider our example from previous section – it only takes a few changes to make the data bound table expand horizontally rather than vertically. Here’s the code rewritten to show customer’s name in the first row of the table, customer’s balance in the second row, with each column corresponding to a customer entry:

•      Visual Basic

Dim doc As New C1PrintDocument()

Dim rt As New RenderTable()

' Next 3 lines set table up for horizontal expansion:

rt.Width = Unit.Auto

rt.ColumnSizingMode = TableSizingModeEnum.Auto

rt.SplitHorzBehavior = SplitBehaviorEnum.SplitIfNeeded

' Define data binding on table columns:

rt.ColGroups(0, 1).DataBinding.DataSource = customers

' Bind column 0 to Name:

rt.Cells(0, 0).Text = "[Fields!Name.Value]"

' Bind column 1 to Balance:

rt.Cells(1, 1).Text = "[Fields(""Balance"").Value]"

' Print negative values in red, positive in blue:

rt.Cells(0, 1).Style.TextColorExpr = "iif(Fields!Balance.Value < 0, Color.Red, Color.Blue)"

' Add the table to the document:

doc.Body.Children.Add(rt)

•      C#

C1PrintDocument doc = new C1PrintDocument();

RenderTable rt = new RenderTable();

// Next 3 lines set table up for horizontal expansion:

rt.Width = Unit.Auto;

rt.ColumnSizingMode = TableSizingModeEnum.Auto;

rt.SplitHorzBehavior = SplitBehaviorEnum.SplitIfNeeded;

// Define data binding on table columns:

rt.ColGroups[0, 1].DataBinding.DataSource = customers;

// Bind column 0 to Name:

rt.Cells[0, 0].Text = "[Fields!Name.Value]";

// Bind column 1 to Balance:

rt.Cells[1, 1].Text = "[Fields(\"Balance\").Value]";

// Print negative values in red, positive in blue:

rt.Cells[0, 1].Style.TextColorExpr =

  "iif(Fields!Balance.Value < 0, Color.Red, Color.Blue)";

// Add the table to the document:

doc.Body.Children.Add(rt);

Note the following changes:

•      The three lines of code following the comment starting with "next 3 lines" ensure that the table's width is based on the sum of columns' widths, that the columns’ width are based on their content, and that the table is allowed to split horizontally if it becomes too wide to fit on a single page.

•      Aside from that, all other changes are basically the result of swapping rows and columns.

Tip 6: Including WinForms controls in a document

It is easy to include a snapshot of a WinForms control from your application in a document that is generated. To do that, use a RenderImage object and its Control property. For instance if your form contains a button button1, this code will include a snapshot of that button within a document:

•      Visual Basic

Dim doc As New C1PrintDocument()

Dim ri As New RenderImage()

ri.Control = Me.button1

doc.Body.Children.Add(ri)

•      C#

C1PrintDocument doc = new C1PrintDocument();

RenderImage ri = new RenderImage();

ri.Control = this.button1;

doc.Body.Children.Add(ri);


Send comments about this topic to ComponentOne.
Copyright © ComponentOne LLC. All rights reserved.