Introduction
Those of you who have had the dubious pleasure of writing code for printing reports in Win32 knows it’s no fun .NET makes it easier but still you have to use the Graphics to draw whatever you want to print, and as we all know using GDI is a pain. So after I implemented a report or two in the old fashion way of copying someone else’s code and changing it to suit my needs in the particular report, I understood there must be another way so I wrote the printing class. The class can be used to easily print any collection as a table.
Using the code
The code contains four classes you need to know:
PrintAttribute
: An attribute you put on the properties to define which properties of the class inside the collection will be printed.
Printing
: A singleton that manages the printing.
PrintingItem
: A class that holds the setting of how a specific property will be printed.
PrintingCol
: A collection of printing items.
Using the printing framework is very simple. All you need to do is:
- Decorate the properties of the type in the collection with the
PrintAttribute
(the print attribute is available only for properties, so everything you want to print needs to be exposed as a property).
public class Item
{
[PrintAttribute("Name", 50, Alignment = StringAlignment.Far,
VerticalAlignment = StringAlignment.Center)]
public string Name
{
get {return name;}
}
[PrintAttribute("My Address", 150)]
public string Address
{
get {return address;}
}
}
- Initialize
Printing.Instance.Data
with the collection (the collection must derive from IEnumareble
). When setting the data property, the Printing
class initializes the PrintingCol
property with all the data about the printing of the collection that was set.
Printing.Instance.Data = arr;
- Initialize the style of the report by setting the
Printing
properties. There are properties available for setting the font of items, and header text and back colors for both items and headers, landscape printing and more.
Printing.Instance.HeaderBackColor = Color.LightGray;
Printing.Instance.RowsBackColor = Color.DarkGray;
- Update the
Printing.Instance.PrintingCol[“Property Name”]
to define styles/visibility/order of specific properties (all of this can be done using the printing attribute also, but this way you can control it in runtime).
- Set the header lines (the header appears in each of the report pages) by setting the
Printing.Instance.HeaderLines
property.
- Call
Printing.Instance.Print()
to start printing.
Extending the framework
In the current version, the framework prints Image
(or its subclasses) as an image and everything else is printed as a string using the object's ToString()
method. The framework has a built in method to specialize the printing of specific types, using the IPrintCell
interface. All the user needs to do is implement the interface for his type, for example:
public class ImageCellPrinter : IPrintCell
{
public float GetHeight(Graphics g, object DataSubItem, Font PrintFont, int Width)
{
Image im = (Image) DataItem;
return im.Height;
}
public void PrintCell(object DataSubItem, RectangleF Bounds,
Graphics g, Font PrintFont, Color BackColor,
Color FrontColor, PrintingItem PrintItem)
{
Image im = (Image) DataSubItem;
g.DrawImage(im, Bounds);
}
}
In this example, I implemented the interface for printing images. All you need to do to add your own type is implement two functions:
GetHeight
: This function is called by the framework to determine the height needed for the cell. The DataSubItem
contains the data to print.
PrintCell
: The framework calls this function to print a cell. The implementation should print the data in DataSubItem
using the Graphics
object.
After implementing the interface, all that is needed is to register it for the type it displays:
Printing.Instance.RegisterPrinter(new ImageCellPrinter(), typeof(Image));
Points of Interest
- The .NET reflection mechanism is a good method for writing generic code in .NET. Using reflection, you can learn everything you need to know about a type at runtime.
- The registration pattern can be used to extend the framework to support specific types without the need to change the code, only by adding new code in accordance with the “open close principle”.