![]() |
Desktop Development »
Miscellaneous »
General
Intermediate
License: The Code Project Open License (CPOL)
Easily turn a ListView into a nicely printed report, complete with print previewBy Phillip PiperThe ListViewPrinter class takes an existing ListView and effortlessly turns it into a pretty report. |
C# 2.0.NET 2.0, Win2K, WinXP, Vista, WinForms, VS2005, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Your shiny new application is finished to perfection � and it's a week ahead of schedule. After your demonstration to The Management, the CEO says, "It's great. I love it. But I want to have all that information as a report. And I want to be able to fiddle with the columns in the report, move them around and resize them, any way I want. It has to be sortable, and groupable, just like we can see on the screen there. And of course, we have to be able to print preview it before we print it. And can we have it finished by tomorrow?" You consider briefly whether you should mention the PrintScreen key, but you doubt he would be enthused with that solution.
Management love reports. Programmers aren't so keen. They often appear as an afterthought to the requirement. Yet producing nice looking reports is not a trivial task. You can always buy one of the commercial products, if you have Blizzard's budget and a few spare months to figure out how to make them work. There is at least one reasonable open source reporting solution for .NET, but the ramp up time is still considerable.
For a lazy and vain programmer like myself, what I really want is something that takes no effort to implement, yet produces wonderful results. I want to be able to go back to my CEO the same day, show him the nice looking reports that do exactly what he wanted, and then remind him about my overdue raise.
The ListViewPrinter is designed to be just such a solution.
The purpose of the ListViewPrinter is simple: it turns a ListView into a pretty report. The resulting report can be print previewed, have it's page setup adjusted, and (of course) be printed.
As normal, one major design goal of ListViewPrinter was that it should quick and easy to use. So, normally, the programmer creates and configures a ListViewPrinter instance within the IDE. Then, it is put into action like this:
this.listViewPrinter1.PrintPreview();
And that is it! Within the IDE, you can say which list view it should print, as well as setting lots of formatting options. And with only that one line of code, you will produce something like this:
![]() |
This is not a screen shot of the list view. This is a print preview of a report that handles paging and shrinking to fit � a report that you can print out and hand to your CEO and finally get that raise you deserve. As you can see from the example, the report has six parts, each of which can be customised:
All of these parts (except the watermark) can be:
The brushes can be gradient, hatch or texture brushes, all of which combine to allow you to make your report as spectacular as you want -- or to make it completely over the top, as you can see was done in the demo. |
The formatting of the output is largely done from within the IDE. Each instance of ListViewPrinter exposes the following properties: HeaderFormat, FooterFormat, ListHeaderFormat, GroupHeaderFormat and CellFormat. These can be unrolled to allow the various aspects to be modified.
This is good but has its limits. Within the IDE there is no standard editor for a Pen or Brush object. The best that can be done for a Brush is to expose a Color and convert that to a SolidBrush. And the best that can be done for a Pen is a width and a color. If you want to use a fancier Brush or Pen (which you really should to get the best looking reports), you will have to write some code.
![]() |
When choosing formatting, you should remember that the report is organised around blocks: the page header and footer, the group and list headers, and the list cells, are blocks. Within a single block, you can specify:
On each side of the block, you can specify:
The diagram shows these settings for the Top and Bottom sides. They can equally be set for the Left and Right sides of the block. |
Much of the flexibility of the formatting comes from the versatility of the Brush class. There are several flavours of Brushes:
SolidBrush (as shown in the diagram) where the area is painted a single color (which can translucent). HatchBrush, where the area is painted with a pattern. TextureBrush, where an area is painted with an image. LinearGradientBrush, where an area is painted with a gradually changing color (as seen in the header and footer above). Time spent understanding the various Brush classes is worth the effort.
Once the configuration is complete, the main programmatic interface to the ListViewPrinter is simple. The commands correspond directly to the main printing commands:
void PageSetup();
void PrintPreview();
void PrintWithDialog();
The idea is that you hook each of these commands into your corresponding menu event handler, like this:
private void pageSetupToolStripMenuItem_Click(object sender,
EventArgs e) {
this.listViewPrinter1.PageSetup();
}
private void printPreviewToolStripMenuItem_Click(object sender, EventArgs e) {
this.listViewPrinter1.PrintPreview();
}
private void printToolStripMenuItem_Click(object sender, EventArgs e) {
this.listViewPrinter1.PrintWithDialog();
}
To print without any user interaction, you can call:
this.listViewPrinter1.Print();
There are always lots of little interesting bits that come up when a project like this is written.
The margins given in the PrintPageEventArgs.MarginBounds are not reliable. Well, technically, they are, but they don't mean what they seem to mean. They work fine during print preview, but when you print a real page, the printout always seems strangely off-center. And the off-centered-ness is different from printer to printer.
The problem is the unreachable areas of the page. There are almost always areas of the page that the printer cannot reach, and these areas are different for different printers. .NET 2.0 represents these unprintable areas as the hard margins.
So, to calculate the effective margin for a printout, the code needs to look like this:
if (this.PrintController.IsPreview)
this.pageBounds = (RectangleF)e.MarginBounds;
else
this.pageBounds = new RectangleF(e.MarginBounds.X -
e.PageSettings.HardMarginX,
e.MarginBounds.Y - e.PageSettings.HardMarginY, e.MarginBounds.Width,
e.MarginBounds.Height);
One thing to keep in mind when printing is that each page is printed separately. Your code is called to print the next page when the framework requires it, and then it exits again. This means that at the end of each page, the printing code has to store enough state information to be able to resume when the next page is required. If you are not used to programming with finite state machines, this statefulness can take a little getting used to.
I'm not sure if anyone actually uses watermarks, but it was fun to implement, and so it is there as a feature (not the recommended method of scoping a project). There are two parts to the watermark problem: how to rotate a string, and how to print the text translucently.
Rotating the text requires either a nice DrawRotatedString() method or messing with matrix transformations. .NET doesn't have a DrawRotatedString() method so we are stuck with transformations. This is actually a good thing, since transformations are far more powerful. General transformations defines how to map points from one coordinate space into another coordinate space. If that doesn't seem very helpful, then you just have to know that they can be used to produce effects such as rotation, scaling, and skewing. .NET does have a nice matrix class, with utility methods to create various transformation matrices. Once we have the transformation matrix we want, we simply give it to our Graphics object, and all drawing after that will be transformed.
So to draw our rotated text, we make a rotation matrix and then apply it to the Graphic object:
Matrix m = new Matrix();
m.RotateAt(watermarkRotation, Utils.CalculateCenter(this.pageBounds));
g.Transform = m;
Printing the text translucently was simply a matter of creating a brush with a translucent color, and then painting the text with that brush:
int alpha = (int)(255.0f * (float)this.WatermarkTransparency /
100.0f);
Brush brush = new SolidBrush(Color.FromArgb(alpha,
this.WatermarkColorOrDefault));
g.DrawString(this.Watermark, this.WatermarkFontOrDefault, brush,
this.pageBounds,
strFormat);
When we have finished drawing the string, we clear the transformation so that drawing returns to normal.
g.ResetTransform();
As you may have guessed from the name, the ListViewPrinter works only with ListViews. If you don't use a ListView to present your data, then ListViewPrinter will not help you. You will have to dig out your checkbook and cancel your social life for a few weeks. But many applications do use ListViews and this class is designed to help.
A ListView must be in Details view for the ListViewPrinter to know how to print it. If it is in any other view, the report will be blank.
The ListViewPrinter cannot print a ListView that is in virtual mode. This is because, in virtual mode, you cannot use the Items collection. So there is no way to get the n'th ListViewItem in the ListView. But see the Shameless Plug below for a way around this limitation.
The ListViewPrinter is not, and is not designed to be, a general purpose reporting solution. There is no provision for running totals, placing graphics, drawing triangles, or having vertical text. It just prints list views.
The ListViewPrinter works well with normal ListViews, but it works even better with ObjectListViews. For example, when used in conjunction with an ObjectListView, subitems can have images, all custom renderers work, and even virtual list views can be printed. For lazy and vain programmers, the ObjectListView is a big help. See here for details about ObjectListView.
Allow Pens and Brushes to be edited directly within the IDE.
Hopefully this code will help you to print out your ListView based data, and to finally get the raise you deserve.
29 November 2007 - Version 1.2
CellFormat.CanWrap = false; Thanks to dgortemaker for the suggestion. ListViewItem has less subitems than there are columns in the list view (thanks to Bernd Melchert) 11 November 2007 - Version 1.0
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 23 Nov 2007 Editor: Sean Ewington |
Copyright 2007 by Phillip Piper Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |