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:
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
:
private ReportPrintingFramework.RPFDesignerControl() rpfDesignerControl1 =
new ReportPrintingFramework.RPFDesignerControl();
If you need dynamic objects, you have to define them. Given below is a sample code snippet:
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"))
}
);
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;
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"))
}
);
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;
this.rpfDesignerControl1.Tables= tables;
Hashtable variables= new Hashtable();
variables["Sample var 1"]= "var1 value";
variables["Sample var 2"]= "var2 value";
variables["Sample var 3"]= "var3 value";
this.rpfDesignerControl1.Variables= variables;
Further, you may want to load an existing file ...
this.rpfDesignerControl1.LoadDocument( filename);
... or save changes to the loaded file ....
this.rpfDesignerControl1.SaveDocument( );
... or make a new copy (e.g. if no document was loaded or you just want to make a new one ) ....
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
:
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:
report_maker.Load( filename);
Create a ReportPrinting
document:
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:
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:
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)