Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

A ReportPrinting Framework

4.82/5 (17 votes)
23 Jul 2010BSD10 min read 2   8.7K  
A Framework to build graphical printing reports with absolute layout based on Mike Mayer's ReportPrinting core library.
ReportPrintingFramework designer's layout sample

ReportPrintingFramework designer's preview sample

Introduction

Some time ago I had to generate simple reports for a job of mine and I was looking for a library that could accomplish the job without the burden of adding all the CrystalReports' DLLs dependencies: CrystalReports is a great tool, providing a rich set of features, but in my experience, most of the times you don't need all that stuff in order to print simple reports.
So I found and tried Mike Mayer's ReportPrinting library. I like that piece of code, even if with some minor issues.
Quite soon I coped with some missing features:

  • I needed to build a report in an absolute layout way
  • I needed my customers be able to personalize the report in some aspect (layout, fonts, color etc.)

So I managed to write a Framework (don't know if this is an appropriate term) around the ReportPrinting library, in order to perform those tasks.

The Framework allows you:

  • To load and save reports to XML files
  • To edit reports with a graphical layout designer, which you can add to your application in order to have your customers personalize the reports.

Keep in Mind ...

The Framework doesn't cover all the features of ReportPrinting, but only the basic ones. You can place objects like text labels, images, lines, print tables and so on but only in a single vertical layout, no multicolumn layout or horizontal page expansion is supported, not all the object properties are supported ... right now, may be some day new features will be added.

I'll do my best to keep this page updated with latest documentation and software version (... if ever there will be any ... ); anyway I suggest you check out this website for latest updates and other stuff.

Background

I suggest Mike Mayer's article though not mandatory for the Framework usage. However, it will be useful if you are interested in the details of the Framework.

ReportPrintingFramework in Detail

If you just want to use the Framework, skip this section and look at the next one, but I suggest a quick survey, anyway.

So, what are the Framework's points of interest?
Let's shortly describe how each task was accomplished.

Report Serialization

To load and save reports, the Framework implements a set of wrapper classes, a class for each of the supported objects (sections, tables, labels etc.). Classes' names are easily recognized, being composed by the RPF suffix and the object name (e.g. RPFLine, RPFLabel and so on).

Each class holds a set of properties (Font, borders, color etc.) which characterize the object: the class provides methods to Serialize and Deserialize from an XML file, and a method for rendering the object to ReportPrinting.ReportBuilder.

Absolute Layout

To get absolute layout, I had to use ReportPrinting.SectionBox: SectionBox can contain a ReportPrinting.LayeredSections and that's what I need. So the Framework defines a uniform way of building the report, using ReportPrinting.LinearLayout (for vertical sections) and ReportPrinting.SectionBox contained within.

Graphical Designer

XML report files are text files, so one could write them starting from a template or from scratch, once the schema is known. But to make life easy, a simple graphical designer control was written.

The designer (RPFDesignerControl) allows you to place objects into the printing surface, in absolute layout fashion. Objects can be dragged from an available resources tree and dropped into the layout surface: placed objects will be available in an allocated resource tree, organized as a hierarchical structure.

Each placed object is hosted into a RPFTrackerBaseControl control. A set of derived RPFTrackerBaseControl classes (one for each RPF object class) provides functionality for the hosted object, e.g. RPFLabelTrackerControl hosts a RPFLabel object and so on.

RPFTrackerBaseControl provides basic functionality like tracker's drawing and mouse's operations (moving, resizing etc.) while each derived object provides object specific features (i.e. the hosted object properties rendering, like font, colors, borders etc.).

The designer control provides a preview tab, too, so that you can directly realize what your finally rendering will be... hopefully the same as layout designer, but better not to trust it too much ...

The Report Structure

Now let's know the reports' unified structure:

  • A report consists of an RPFDocument object: The RPFDocument holds global report's properties like the printer name and the page size
  • An RPFDocument consists of RPFSections: Each section (or container as called in designer) is vertically piled according to the insertion order.
    There are two kinds of containers:
    • Fixed sections (RPFFIxedSection): This container is named fixed because the vertical size is fixed at design time, i.e. it cannot expand dynamically and doesn't split across pages.
    • Table rows (RPFTableRowSection): This container provides tables' printing. At design time, you can set up the single row layout and properties (colours, borders etc.) and column's properties, as well. At runtime (when printing) the single row is repeated according to the real table's row, automatically splitting across pages. Table rows can host the specific Table column and any other "static" or "dynamic" object.

    Each container is an absolute layout surface where you can place RPFObjects: We have different kinds of RPFObjects:

    • Static objects: i.e. object which is completely defined at design time:
      • Boxes (RPFBox): Draws a simple box
      • Images (RPFImage): Draws an image. Once selected from an existing file, the image is embedded into the XML report file.
      • Text labels (RPFLabel): Draws a fixed text label
      • Lines (RPFLine): Draws a vertical or horizontal line
    • Dynamic objects: i.e. object whose value is defined at run time (at design time, you can only specify it's properties based on place holder values):
      • Variables (RPFVariable): Draws a variable text label, i.e. a label whose value is defined at the time of printing.
      • Table columns (RPFTableColumn): Draws the value of the specific table column. A table column can be placed only into the relative RPFTableRowSection container.

      While static objects are fixed designer resource, dynamic objects must be defined by your application, i.e. you must provide the collection of tables and of variables, both at design time (values will be place holders) and at run time. If you don't need tables or variables just leave the collection empty.

Using the Code

The ReportPrintingFramework needs just a few lines of code to be ready for your application.

We can exploit two typical scenarios: design time and run time
(For further details, please have a look at ReportPrintingFrameworkDemo source code).

Design Time: Using the Designer (RPFDesignerControl)

To create a report file, you may want to use the designer.

First of all, you need a reference to ReportPrintingFramework and ReportPrinting, as well.
The RPFDesignerControl is a control so you need to insert it into a container: most of the time, you will create a Windows Form with some button or menu command to load and save the document (or whatever else you need) and an instance of the RPFDesignerControl:

C#
private ReportPrintingFramework.RPFDesignerControl() rpfDesignerControl1 =
        new ReportPrintingFramework.RPFDesignerControl();

If you need dynamic objects, you have to define them. Given below is a sample code snippet:

C#
// Add sample tables:
// You can change tables names, column structure and number 
// according to your report needs

// First table ...
Hashtable tables= new Hashtable();
System.Data.DataTable sample_table_1= new System.Data.DataTable();

sample_table_1.Columns.AddRange( new System.Data.DataColumn[] {
    new System.Data.DataColumn( "ID", System.Type.GetType( "System.Int32")),
    new System.Data.DataColumn( "NAME", System.Type.GetType( "System.String")),
    new System.Data.DataColumn( "SURNAME", 
                System.Type.GetType( "System.String"))
    }
);
// Add place holder rows:
for ( int i= 0; i <= 50; i++) {
    sample_table_1.Rows.Add( new object[] 
        { i, string.Format( "NAME {0}", i), string.Format( "SURNAME {0}", i)});
}
tables[ "Sample table 1"]= sample_table_1;

// and a second one
System.Data.DataTable sample_table_2= new System.Data.DataTable();
sample_table_2.Columns.AddRange( new System.Data.DataColumn[] {
        new System.Data.DataColumn( "DATE", System.Type.GetType
                        ( "System.DateTime")),
        new System.Data.DataColumn( "LOCATION", System.Type.GetType
                        ( "System.String"))
    }
);
// Add place holder rows:
sample_table_2.Rows.Add( new object[] 
    { new DateTime( 2007, 12, 25) , "ROME"});
sample_table_2.Rows.Add( new object[] 
    { new DateTime( 2006, 12, 25) , "FLORENCE"});

tables[ "Sample table 2"]= sample_table_2;

// Assign tables to designer
this.rpfDesignerControl1.Tables= tables;

// Add sample variables
// You can change variable names and number according to your report needs
Hashtable variables= new Hashtable();
variables["Sample var 1"]= "var1 value";
variables["Sample var 2"]= "var2 value";
variables["Sample var 3"]= "var3 value";

// Assign variables to designer
this.rpfDesignerControl1.Variables= variables;

Further, you may want to load an existing file ...

C#
this.rpfDesignerControl1.LoadDocument( filename);

... or save changes to the loaded file ....

C#
this.rpfDesignerControl1.SaveDocument( );

... or make a new copy (e.g. if no document was loaded or you just want to make a new one ) ....

C#
this.rpfDesignerControl1.SaveAsDocument( filename);

Run Time: Printing a Report (RPFReportMaker)

Once you get a report file, you need to print it. This can be accomplished following these steps:

Have an instance of RPFReportMaker:

C#
ReportPrintingFramework.RPFReportMaker report_maker= 
    new ReportPrintingFramework.RPFReportMaker();

If you need dynamic objects, you have to define them: mostly like code for designer, so I don't repeat that, but with real values now.

Load the report file:

C#
report_maker.Load( filename);

Create a ReportPrinting document:

C#
ReportPrinting.ReportDocument report_document= 
    new ReportPrinting.ReportDocument();

report_document.Body = null;
report_document.PageFooter = null;
report_document.PageFooterMaxHeight = 0F;
report_document.PageHeader = null;
report_document.PageHeaderMaxHeight = 0F;
report_document.ResetAfterPrint = true;

Set up some report property:

C#
report_document.DefaultPageSettings.Landscape= 
    report_maker.RPFDocument.Landscape;
report_document.DefaultPageSettings.Margins= 
    new System.Drawing.Printing.Margins(
        (int)(float)(report_maker.RPFDocument.Margins.Left* 100.0f),
        (int)(float)(report_maker.RPFDocument.Margins.Right* 100.0f),
        (int)(float)(report_maker.RPFDocument.Margins.Top* 100.0f),
        (int)(float)(report_maker.RPFDocument.Margins.Bottom* 100.0f));

Assign the RPFReportMaker to ReportPrinting.ReportDocument attribute:

C#
report_document.ReportMaker= report_maker;

That's all, now you can print ReportPrinting.ReportDocument in the usual way.

Points of Interest

Not so much of my own... just collecting and extending another guys' job.
I can just point out some features you can find browsing the code:

  • A globalized property grid implementation (see credits)
  • Drag and drop between different controls
  • A simple tracker control
  • A method for retrieving installed printers
  • A method for retrieving PageSize from PaperKind
  • Some XML serialization tips

Known Bugs

I didn't exhaustively test the Framework, so I expect some bugs ...
Anyway, some are already known and still pending:

  • Using Ctrl+X or Del in a property grid text field to cut or delete text has the side effect to Cut or Delete the designer selected objects!! Not a nice effect: use Backspace indeed and don't cut text!
  • When working with large page format (not completely fitting into the screen), selecting an object will scroll the hosting control to the top left corner (quite related to Visual Studio behavior). No workaround suggested (it would probably be fixed when designer zoom will be implemented).
  • When working with thin line objects, tracking operation may be difficult. Temporarily enlarge the line thickness (through property sheet), resize the line and restore original thickness. (it would probably be fixed when outside boundaries tracker control will be implemented).

To Do

This is a working release, enough for my job, but much more work may be done... depending on your feedback and my spare time....

Just some hints:

  • Add more properties to existing objects
  • Add support for flow layout containers
  • Add designer grid and snap to grid commands
  • Add designer Undo/ Redo
  • Add designer Zoom
  • Improve tracker control (i.e. draw tracker outside control boundaries)
  • Some code refactoring and XML comments
  • ... and much more ...

Credits

History

  • July-2010: Porting to .NET 3.5 and Visual Studio 2008 Express, some bug fixes and improvements
    • ReportPrintingFramework rev. 0.2.0
      • Updated reference to ReportPrinting.dll rev 0.153 (.NET 3.5 ported library version)
      • RPFDesignerControl:
        • BUG-FIX: Bounded the PreviewControl start page assignment to valid values to avoid exceptions
        • Added PreviewControl.InvalidatePreview() method call when assigning PreviewControl.Document (on .NET 3.5, it doesn't automatically updates as in .NET 1.1)
      • RPFDocument:
        • BUG-FIX: Assigned PrinterSetting.PrinterName to null if value is empty or null. This allows using the default system printer when no printer value is assigned.
      • Added design time capability to insert more sections than the document can hold so it can span pages with fixed sections too: The single section height must be lower than the document one.
      • Added item visibility property based on a variable value or on the item itself value (when applicable)
      • BUG-FIX: Keyboard shortcut (copy, paste, cut, etc.) are now correctly handled only when a designer control has focus
      • BUG-FIX: When the printer name wasn't a valid printer for the system, GetPaperSize returned a null PaperSize value causing the designer size to collapse. Now GetPaperSize tries to get PaperSize from the default system printer.
      • BUG_FIX: The m_designer_layout_panel.AutoScroll property set caused an odd panel scroll when selecting or dragging a child Tracker control. A custom DesignerLayoutControl was derived from Panel, with AutoScroll= false and AdjustFormScrollbars called on panel layout.
        NOTE: As a side effect, now, the panel isn't refreshed when tracking the scrollbars track.
    • ReportPrintingFrameworkDemo rev. 0.1.1
      • Updated reference to ReportPrinting.dll rev 1.5.3 and ReportPrintingFramework rev 0.2.0 (.NET 3.5 ported library version) 
    • ReportPrinting rev. 0.153 (the patched version to work with ReportPrintingFramework 0.2.0
  • November-2007: First emission
    • ReportPrintingFramework rev. 0.1.2
    • ReportPrintingFrameworkDemo rev. 0.1.0
    • ReportPrinting rev. 0.5.2 (the patched version to work with ReportPrintingFramework: I don't know if an official 0.5.2 version already exists. This is a modified version taken from rev. 0.5.1)

License

This article, along with any associated source code and files, is licensed under The BSD License