Sunday, February 8, 2004

Printing Reports - Tutorial


Printing a document programmatically is quite involved. Using the ReportPrinting library presented here, youll be able to print reports with multiple sections, with very little code.

Figure 1 - Part of a sample report

This report is comprised of plain text sections (such as the title "Birthdays", and the other paragraphs) and grids of data from a database (more specifically, from a DataView object).


This page will take you through the process of using ReportPrinting library, step by step.

Create a DataView

The first step is to create a DateTable and DateView that serves as the source of data. The following code will create a table of some famous birthdays:

public static DataView GetDataView()
    DataTable dt = new DataTable("People");
    dt.Columns.Add("FirstName", typeof(string));
    dt.Columns.Add("LastName", typeof(string));
    dt.Columns.Add("Birthdate", typeof(DateTime));

    dt.Rows.Add(new Object[] {"Theodore", "Roosevelt",  new DateTime(1858, 11, 27)});
    dt.Rows.Add(new Object[] {"Winston",  "Churchill",  new DateTime(1874, 11, 30)});
    dt.Rows.Add(new Object[] {"Pablo",    "Picasso",    new DateTime(1881, 10, 25)});
    dt.Rows.Add(new Object[] {"Charlie",  "Chaplin",    new DateTime(1889,  4, 16)});
    dt.Rows.Add(new Object[] {"Steven",   "Spielberg",  new DateTime(1946, 12, 18)});
    dt.Rows.Add(new Object[] {"Bart",     "Simpson",    new DateTime(1987,  4, 19)});
    dt.Rows.Add(new Object[] {"Louis",    "Armstrong",  new DateTime(1901,  8,  4)});
    dt.Rows.Add(new Object[] {"Igor",     "Stravinski", new DateTime(1882,  6, 17)});
    dt.Rows.Add(new Object[] {"Bill",     "Gates",      new DateTime(1955, 10, 28)});
    dt.Rows.Add(new Object[] {"Albert",   "Einstein",   new DateTime(1879,  3, 14)});
    dt.Rows.Add(new Object[] {"Marilyn",  "Monroe",     new DateTime(1927,  6,  1)});
    dt.Rows.Add(new Object[] {"Mother",   "Teresa",     new DateTime(1910,  8, 27)});

    DataView dv = dt.DefaultView;
    return dv;

This function will return a DataView for a table of a dozen famous individuals and their birthdays.

Create a ReportMaker

The ReportPrinting.IReportMaker interface is used to create objects that setup a ReportDocument. The ReportMaker for this example is called SampleReportMaker1. It has one method to implement:

public void MakeDocument(ReportDocument reportDocument)

Let's take a look at the implementation of this method step-by-step. First, it is a good idea to reset the TextStyle class. This class provides global styles for text blocks. Since the scope of this class is application wide, it should be reset to a known state.


As mentioned, the TextStyle class has several static, global styles that can be applied to different text blocks. These styles can each be customized. We'll change some fonts and colors just to show what's possible.

    // Setup the global TextStyles
    TextStyle.Heading1.FontFamily = new FontFamily("Comic Sans MS");
    TextStyle.Heading1.Brush = Brushes.DarkBlue;
    TextStyle.Heading1.SizeDelta = 5.0f;  
    TextStyle.TableHeader.Brush = Brushes.White;
    TextStyle.TableHeader.BackgroundBrush = Brushes.DarkBlue;
    TextStyle.TableRow.BackgroundBrush = Brushes.AntiqueWhite;
    TextStyle.Normal.Size = 12.0f;
    // Add some white-space to the page.  By adding a 1/10 inch margin
    // to the bottom of every line, quite a bit of white space will be added
    TextStyle.Normal.MarginBottom = 0.1f;

Using our class defined earlier, we'll get a dataview and set the default sort based on the values setup in a GUI.  (Note, this is a hack. Better encapsulation should be used to isolate the dialog, defined later, from this class.)

    // create a data table and a default view from it.
    DataView dv = GetDataView();
    // set the sort on the data view
    if (myPrintDialog.cmbOrderBy.SelectedItem != null)
        string str = myPrintDialog.cmbOrderBy.SelectedItem.ToString();
        if (myPrintDialog.chkReverse.Checked)
            str += " DESC";
        dv.Sort = str;

The next step is creating an instance of the ReportPrinting.ReportBuilder class. This object is used to simplify the somewhat complicated task of piecing together text, data, and the container sections that they go into.

    // create a builder to help with putting the table together.
    ReportBuilder builder = new ReportBuilder(reportDocument);

Creating a header and footer are quite easy with the buider class' five overloaded functions. This one creates a simple header with text on the left side (Brithdays Report) and on the right side (page #). The footer has the date centered.

    // Add a simple page header and footer that is the same on all pages.
    builder.AddPageHeader("Birthdays Report", String.Empty, "page %p");
    builder.AddPageFooter(String.Empty, DateTime.Now.ToLongDateString(), String.Empty);

Now, the real fun begins. We start a vertical, linear layout because every section from here should be added below the preceding section (see Layouts for more information).


Now add two text sections. The first section added will be a heading (as defined by TextStyle.Heading1). The second section is just normal text (as defined by the TextStyle.Normal).

    // Add text sections 
    builder.AddText("Birthdays", TextStyle.Heading1); 
    builder.AddText("The following are various birthdays of people who " + 
        "are considered important in history."); 

Next, we add a section of a data table. The first line adds a data section with a visible header row. Then three column descriptors are added. These are added in the order that the columns are displayed. That is, LastName will be the first column, followed by FirstName, followed by Birthdate.

The first parameter passed to AddColumn is the name of the column in the underlying DataTable. The second parameter is the string as printed in the header row. The last three parameters describe the widths used. A max-width can be specified in inches. Optionally, the width can be auto-sized based on the header row and/or the data rows. In this case, with false being passed, no auto-sizing is being done.

    Add a data section, then add columns builder.AddTable 
    (dv, true);
    builder.AddColumn ("LastName", "Last Name", 1.5f, false, false);
    builder.AddColumn ("FirstName", "First Name", 1.5f, false, false);
    builder.AddColumn ("Birthdate", "Birthdate", 3.0f, false, false);

We set a format expression for the last column added (the date column). These format expressions are identical to those used by String.Format

    // Set the format expression to this string. 
    builder.CurrentColumn.FormatExpression = "{0:D}"; 

And the very last thing is to finish the LinearLayout that was started earlier.


Create a form for printing

There are only a handful of controls on the following form: a label, a combox box, a check box, and a usercontrol from ReportPrinting namespace called PrintControl.  This control has the four buttons you see at the bottom of the form.

Figure 2 - SampleDialog

This form also has an instance of ReportPrinting.ReportDocument class. This is a subclass of System.Drawing.Printing.PrintDocument. If you create the above form in a designer, here is the constructor required to create a new ReportDocument object.

private ReportDocument reportDocument;
public ReportPrinting.PrintControl PrintControls;
public System.Windows.Forms.ComboBox cmbOrderBy;
public System.Windows.Forms.CheckBox chkReverse;

public SamplePrintDialog1()

	this.reportDocument = new ReportDocument();
	this.PrintControls.Document = reportDocument;

	SampleReportMaker1 reportMaker = new SampleReportMaker1(this);

	this.reportDocument.ReportMaker = reportMaker;


First an instance is created with new ReportDocument(). This instance is assigned to the PrintControls.Document property. A SampleReportMaker1 object (defined above) is then created and assigned to the ReportDocument's ReportMaker property. The final bit of the constructor simply sets up the combobox.


That's all that is involved in printing a document.  Of course, you can use the standard PrintDialog, PrintPreview, and PageSettings dialogs on your own (without using the PrintControls usercontrol).

This entire sample can be found in the download of the ReportPrinting library, along with many more.

No comments: