Click here to Skip to main content
15,867,756 members
Articles / Desktop Programming / Windows Forms

Easily turn a ListView into a nicely printed report, complete with print preview

Rate me:
Please Sign up or sign in to vote.
4.94/5 (76 votes)
23 Nov 2007CPOL9 min read 240.8K   18.7K   173   73
The ListViewPrinter class takes an existing ListView and effortlessly turns it into a pretty report.
Screenshot - ReportOTTExample.jpg

Introduction

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 ListViewPrinter 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:

C#
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:

Screenshot - Report1.jpg

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:

  1. the page header
  2. the list view header
  3. a group header (only shown if the list view being printed was showing groups)
  4. the rows from the list
  5. the page footer
  6. the watermark

All of these parts (except the watermark) can be:

  • told what font and brush to use for the text
  • have it's background painted with any brush
  • have any or all of its borders painted with any pen

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.

Controlling the Format

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.

Screenshot - BlockFormat.png

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:

  • the font to be used for the text
  • the brush used to paint the text
  • the brush used to fill in the area between the borders.

On each side of the block, you can specify:

  • the padding (how much whitespace should separate this block from its neighbour)
  • the pen used to draw the border. This includes the width of the pen and the brush to be used for drawing the border.
  • the text inset (how much space should be left between the border and the text)

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:

  • the SolidBrush (as shown in the diagram) where the area is painted a single color (which can translucent).
  • the HatchBrush, where the area is painted with a pattern.
  • the TextureBrush, where an area is painted with an image.
  • the 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.

Getting Down to Business

Once the configuration is complete, the main programmatic interface to the ListViewPrinter is simple. The commands correspond directly to the main printing commands:

C#
void PageSetup();
void PrintPreview();
void PrintWithDialog();

The idea is that you hook each of these commands into your corresponding menu event handler, like this:

C#
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:

C#
this.listViewPrinter1.Print();

Interesting Bits of Code

There are always lots of little interesting bits that come up when a project like this is written.

Margins for Error

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:

C#
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);

Stateful Execution

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.

Printing the Watermarks

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:

C#
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:

C#
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.

C#
g.ResetTransform();

Limitations

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.

Shameless Plug

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.

Still To Do

Allow Pens and Brushes to be edited directly within the IDE.

Conclusion

Hopefully this code will help you to print out your ListView based data, and to finally get the raise you deserve.

History

29 November 2007 - Version 1.2

  • List rows now wrap text rather than ellipsing it. You can turn off this behaviour by CellFormat.CanWrap = false; Thanks to dgortemaker for the suggestion.
  • Handle the case where a ListViewItem has less subitems than there are columns in the list view (thanks to Bernd Melchert)

11 November 2007 - Version 1.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
Australia Australia
Phillip has been playing with computers since the Apple II was the hottest home computer available. He learned the fine art of C programming and Guru meditation on the Amiga.

C# and Python are his languages of choice. Smalltalk is his mentor for simplicity and beauty. C++ is to programming what drills are to visits to the dentist.

He worked for longer than he cares to remember as Lead Programmer and System Architect of the Objective document management system. (www.objective.com)

He has lived for 10 years in northern Mozambique, teaching in villages.

He has developed high volume trading software, low volume FX trading software, and is currently working for Atlassian on HipChat.

Comments and Discussions

 
QuestionTotal for the Amount Column. Pin
Member 92447719-May-20 1:36
Member 92447719-May-20 1:36 
QuestionMore Lines/details in header Pin
Member 92447718-May-20 19:38
Member 92447718-May-20 19:38 
QuestionTotal Pages in footer Pin
Member 92447718-May-20 19:36
Member 92447718-May-20 19:36 
AnswerRe: Total Pages in footer Pin
Member 92447719-May-20 1:33
Member 92447719-May-20 1:33 
GeneralMy vote of 5 Pin
Achim Bohmann16-Feb-19 23:12
Achim Bohmann16-Feb-19 23:12 
QuestionPrint only checkObjects with ListView Printer Pin
ArpitNagar13-May-16 5:14
ArpitNagar13-May-16 5:14 
QuestionRightToLeftLayout ? Pin
cahill29-Nov-15 5:17
cahill29-Nov-15 5:17 
QuestionHow Can I Add a page to the END? Pin
FinalByte23-Jan-15 3:49
FinalByte23-Jan-15 3:49 
QuestionHeader/Footer WrapText Pin
Member 108291058-Dec-14 4:01
Member 108291058-Dec-14 4:01 
QuestionHow can I Add Pages? Pin
FinalByte18-Nov-14 1:10
FinalByte18-Nov-14 1:10 
AnswerRe: How can I Add Pages? Pin
FinalByte20-Nov-14 20:04
FinalByte20-Nov-14 20:04 
QuestionTreeListView & ListViewPrinter Pin
HMVC27-Jul-14 13:01
HMVC27-Jul-14 13:01 
QuestionRotate page programmatically. Pin
Vadym Rybak12-Jan-14 8:31
professionalVadym Rybak12-Jan-14 8:31 
QuestionList view printing Pin
Chathu02018-Nov-13 17:22
Chathu02018-Nov-13 17:22 
AnswerRe: List view printing Pin
Phillip Piper18-Nov-13 17:32
Phillip Piper18-Nov-13 17:32 
GeneralMy vote of 5 Pin
Huseyin Atasoy18-Apr-13 22:39
Huseyin Atasoy18-Apr-13 22:39 
QuestionDetails please PinPopular
sourabhkumbhar8-Jan-13 2:50
sourabhkumbhar8-Jan-13 2:50 
QuestionListViewPrinter Groups Pin
linebery10-Jan-12 6:56
linebery10-Jan-12 6:56 
GeneralMy vote of 5 Pin
Zipadie Doodah19-Apr-11 3:28
Zipadie Doodah19-Apr-11 3:28 
GeneralMy vote of 4 Pin
Zipadie Doodah18-Apr-11 5:34
Zipadie Doodah18-Apr-11 5:34 
Excellent Code, here is an update for printing an indented cell and using the same font/color of the cell as in the listview.

replace method PrintCell with the following code bit:

virtual protected void PrintCell(Graphics g, ListView lv, ListViewItem lvi, int row, int column, RectangleF cell)
{
    BlockFormat fmt = this.CellFormat;
    ColumnHeader ch = this.GetColumn(column);
    float textIndent = 0;

    if (ch.DisplayIndex == 0) { textIndent = (lvi.IndentCount * 5); }

    fmt.TextBrush = new System.Drawing.SolidBrush(lvi.ForeColor);   // Set the cell color.
    fmt.Font = lvi.Font;                                            // Set the font of this cell.

    // Are we going to print an icon in this cell? We print an image if it
    // isn't a text only report AND it is a primary column AND the cell has an image and a image list.
    if (!this.IsTextOnly && ch.Index == 0 && lvi.ImageIndex != -1 && lv.SmallImageList != null) {
        // Trick the block format into indenting the text so it doesn't write the text into where the image is going to be drawn
        const int gapBetweenImageAndText = 3;
        float textInsetCorrection = lv.SmallImageList.ImageSize.Width + gapBetweenImageAndText;

        fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) + textInsetCorrection + textIndent);
        fmt.Draw(g, cell, this.GetSubItem(lvi, column).Text, ch.TextAlign);
        fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) - textInsetCorrection);

        // Now draw the image into the area reserved for it
        RectangleF r = fmt.CalculatePaddedTextBox(cell);
        if (lv.SmallImageList.ImageSize.Height < r.Height)
            r.Y += (r.Height - lv.SmallImageList.ImageSize.Height) / 2;
        g.DrawImage(lv.SmallImageList.Images[lvi.ImageIndex], r.Location);

        fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) - textIndent);
    } else {
        // No image to draw. SImply draw the text

        fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) + textIndent);
        fmt.Draw(g, cell, this.GetSubItem(lvi, column).Text, ch.TextAlign);
        fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) - textIndent);
    }
}

Laugh | :laugh:

modified on Tuesday, April 19, 2011 9:23 AM

GeneralMy vote of 5 Pin
Mario Majčica2-Jan-11 1:46
professionalMario Majčica2-Jan-11 1:46 
GeneralText is in one corner [modified] Pin
Juna Thakuri29-Nov-10 5:38
Juna Thakuri29-Nov-10 5:38 
GeneralRe: Text is in one corner Pin
spekkie10-Mar-11 22:52
spekkie10-Mar-11 22:52 
QuestionListView with Groups Pin
powermetal11421-Sep-10 15:36
powermetal11421-Sep-10 15:36 
QuestionListviewPrinter Pin
ssonuh10-Sep-09 6:08
ssonuh10-Sep-09 6:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.