PrintDocument for WinForms | ComponentOne
Best Practices / PrintDocument
In This Topic
    PrintDocument
    In This Topic

    Some of the best practices to follow while working with the PrintDocument component are listed below:

    Tip 1: StartDoc()-EndDoc() vs Generate() methods

    C1PrintDocument supports two different approaches to create documents: StartDoc-EndDoc and Generate methods.

    StartDoc()-EndDoc() Methods

    The StartDoc method marks the start of generation of the document and the EndDoc finishes the generation of the document. The StartDoc-EndDoc is used when the render objects are to be added into the block flow of the document i.e. using the RenderBlock(), RenderDirect() and RenderInline() methods as follows:

    C#
    Copy Code
    c1PrintDocument1.RenderBlock(renderTable1);
    

    Generate() Method The Generate method generates the document using the RefreshCalculatedValues refresh mode. You may refer to this link to gather more info about the RefreshModes. The Generate method is used when the render objects are added directly into the body of the document using the code as following:

    C#
    Copy Code
    this.c1PrintDocument1.Body.Children.Add(new C1.C1Preview.RenderText("Hello, World!"));
    
    Note: Since both the methods are called to generate the document, they can't be used together. If using the StartDoc, the document generation has already been started, hence one cannot make a call to refresh or regenerate the document in between by using the Generate method.

    Insert new Page using StartDoc and Generate() methods:

    1. When using StartDoc()-EndDoc() methods, a new page can easily be inserted using the NewPage method of the C1PrintDocument as shown in the below code:
      C#
      Copy Code
      c1PrintDocument1.RenderBlock(renderTable1); 
      c1PrintDocument1.NewPage(); 
      c1PrintDocument1.RenderBlock(renderTable2);
      
    2. In case you are using Generate() method, to generate the document, you would need to insert the page break before or after a Render object added to the document as illustrated in the following code snippet that inserts a new page between two Render Tables:
      C#
      Copy Code
      c1PrintDocument1..Body.Children.Add(renderTable1); 
      rt1.BreakAfter = BreakEnum.Page; 
      c1PrintDocument1.Body.Children.Add(renderTable2); 
      c1PrintDocument1.Generate();
      

    Tip 2: 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:

    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 PrintDocument for WinForms.

    Tip 3: 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:

    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:

    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:
      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;
      
    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:
      C#
      Copy Code
      RenderParagraph codeLine = new RenderParagraph();   
      MessageBox.Show("Hello 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 4: 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:

    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.

    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:

    C#
    Copy Code
    doc.PageLayout.PageHeader = new RenderText("[iif(PageNo=1, \"First page\", " + "string.Format(\"Page {0} of {1}\", PageNo, 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:

    C#
    Copy Code
    Type your example code here. It will be automatically colorized when you switch to Preview or build the help system.
    

    Tip 5: 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:

    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:

    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:

    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:

    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:

    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:

    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:

    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:

    C#
    Copy Code
    doc.ScriptingOptions.Language = C1.C1Preview.Scripting.ScriptLanguageEnum.CSharp;
    

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

    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:

    Tip 6: Customizing Data-bound 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:

    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:

    Tip 7: 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:

    C#
    Copy Code
    C1PrintDocument doc = new C1PrintDocument();   
    RenderImage ri = new RenderImage();   
    ri.Control = this.button1;   
    doc.Body.Children.Add(ri);