The following tips relate to the C1PrintDocument component.
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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" |
To write code in C#
C# |
Copy Code
|
---|---|
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 Reports for WinForms.
When rendered, paragraphs and other C1PrintDocument objects have styles that can be modified "inline". For example, like this:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
Dim RenderText rt As New RenderText("testing...") rt.Style.TextColor = Color.Red |
To write code in C#
C# |
Copy Code
|
---|---|
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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 |
To write code in C#
C# |
Copy Code
|
---|---|
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; |
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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) |
To write code in C#
C# |
Copy Code
|
---|---|
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.
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
Dim doc As New C1PrintDocument() doc.PageLayout.PageHeader = New RenderText("[iif(PageNo=1, ""First page"", ""Page "" & PageNo & "" of "" & PageCount)]") |
To write code in C#
C# |
Copy Code
|
---|---|
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 C1PrintDocument Language property must be set to C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp. Using C# as the expression language, our example would look like this:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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]") |
To write code in C#
C# |
Copy Code
|
---|---|
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:
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
doc.PageLayout.PageHeader = New RenderText("[iif(PageNo=1, ""First page"", " & "string.Format(""Page {0} of {1}"", PageNo, PageCount))]") |
To write code in C#
C# |
Copy Code
|
---|---|
doc.PageLayout.PageHeader = new RenderText("[iif(PageNo=1, \"First page\", " + "string.Format(\"Page {0} of {1}\", PageNo, PageCount))]"); |
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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 |
To write code in C#
C# |
Copy Code
|
---|---|
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
' 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 |
To write code in C#
C# |
Copy Code
|
---|---|
// 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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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) |
To write code in C#
C# |
Copy Code
|
---|---|
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
' Define data binding on table rows: rt.RowGroups(0, 1).DataBinding.DataSource = customers) |
To write code in C#
C# |
Copy Code
|
---|---|
// 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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
' 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]" |
To write code in C#
C# |
Copy Code
|
---|---|
// 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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
rt.Cells(0, 1).Style.TextColorExpr = "iif(Fields!Balance.Value < 0, Color.Red, Color.Blue)" |
To write code in C#
C# |
Copy Code
|
---|---|
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
ro.Style.BackColorExpr = "[iif(PageNo > 10, Color.Red, Color.Green)]" |
To write code in C#
C# |
Copy Code
|
---|---|
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp |
To write code in C#
C# |
Copy Code
|
---|---|
doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp; |
With this in mind, our current sample may be rewritten as follows:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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) |
To write code in C#
C# |
Copy Code
|
---|---|
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:
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
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) |
To write code in C#
C# |
Copy Code
|
---|---|
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:
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:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
Dim doc As New C1PrintDocument() Dim ri As New RenderImage() ri.Control = Me.button1 doc.Body.Children.Add(ri) |
To write code in C#
C# |
Copy Code
|
---|---|
C1PrintDocument doc = new C1PrintDocument(); RenderImage ri = new RenderImage(); ri.Control = this.button1; doc.Body.Children.Add(ri); |