Using a ListCtrlPrinter

The slothful way to love reports

Using a ListCtrlPrinter

A ListCtrlPrinter takes an ObjectListView (or plain old wx.ListCtrl) and effortlessly turns it into a pretty report. With only two lines of code, you can produce a nice report like this:

_images/listctrlprinter-example1.png

When would I want to use it?

Your shiny new application is finished to perfection — and it’s a week ahead of schedule (we’re imagining here, so let’s be completely unrealistic). 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, 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. If you using other languages, you can buy a commercial product (if you have Blizzard’s budget), but with wxPython, there are not that many options. You normally have to write it all yourself, which can be frustrating. wx’s printing scheme can cause even strong programmers to go weak in the knees – even with Robin’s wonderful book for reference.

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 ListCtrlPrinter is designed to be just such a solution.

How do I use it in my project?

As always, the goal is for this to be as easy to use as possible. A typical usage should be as simple as:

printer = ListCtrlPrinter(self.myListCtrl, "My Report Title")
printer.PrintPreview()

This code create a new ListCtrlPrinter, telling it what ListCtrl it should print and what the report should be titled. Then a print preview of the report is opened.

A more complete example might look like this:

printer = ListCtrlPrinter(self.lv, "Graviton Collision Statistics")
printer.ReportFormat = ReportFormat.Normal("Lucida Bright")
printer.ReportFormat.IsColumnHeadingsOnEachPage = True

printer.PageHeader("Monthly Bad Science Report")
printer.PageFooter("Bright Ideas Software", "%(date)s", "%(currentPage)d of %(totalPages)d")
printer.Watermark("Work hard!")

printer.PrintPreview(self)

Primary commands

The ListCtrlPrinter has commands that match the normal printing commands, which should be hooked into the menu structure of your application.

  • PageSetup()

    Opens the normal “Page Setup” dialog that allows the user to choose the page orientation and margins, as well as choose the printer for output.

  • PrintPreview()

    Open a print preview dialog, from which the user can also print the report.

  • Print()

    Opens the “Print” dialog that lets the user choose which printer, the pages and other details before printing the report.

Working in a structured environment

A report consists of several blocks, each of which stacks vertically. The structure of a report is like this:

_images/listctrlprinter-structure.png

The PageHeader and PageFooter repeat on each page (obviously). The ColumnHeader repeats on each page, depending on the ReportFormat options.

Controlling the appearance

The formatting of a report is controlled completely by the ReportFormat object of the ListCtrlPrinter. To change the appearance of the report, you change the settings in this object.

These properties control the appearance of the report as a whole:

  • IncludeImages

    Should images from the ListCtrl be included in the report?

  • IsColumnHeadingsOnEachPage

    If this is True, the column headers will be repeated at the top of each page.

  • IsShrinkToFit

    If this is True, the report will be shrunk so that all the column of the ListCtrl can fit within the width of page.

  • UseListCtrlTextFormat

    If this is True, the format (text font and color) of the rows will be taken from the ListCtrl itself, rather than the Cell.BlockFormat object. Useful if your ListCtrl has fancy formatting on the rows that you want to replicate in the printed version.

As was illustrated above, a report consists of various sections (called “blocks”). Each of these blocks has a matching BlockFormat object in the ReportFormat. To modify the appearance of a block, you modify its matching BlockFormat object. So, to modify the format of the page header, you change the ReportFormat.PageHeader object.

A ReportFormat object has the following properties which control the appearance of the matching sections of the report:

  • PageHeader
  • ListHeader
  • ColumnHeader
  • GroupTitle
  • Row
  • ListFooter
  • PageFooter

These properties return BlockFormat objects, which have the following properties:

  • CanWrap

    If the text for this block cannot fit horizontally, should be wrap to a new line (True) or should it be truncated (False)?

  • Font

    What font should be used to draw the text of this block

  • Padding

    How much padding should be applied to the block before the text or other decorations are drawn? This can be a numeric (which will be applied to all sides) or it can be a collection of the paddings to be applied to the various sides: (left, top, right, bottom).

  • TextAlignment

    How should text be aligned within this block? Can be wx.ALIGN_LEFT, wx.ALIGN_CENTER, or wx.ALIGN_RIGHT.

  • TextColor

    In what color should be text be drawn?

The blocks that are based on cells (PageHeader, ColumnHeader, Row, PageFooter) can also have the following properties set:

  • AlwaysCenter

    Will the text in the cells be center aligned, regardless of other settings?

  • CellPadding

    How much padding should be applied to this cell before the text or other decorations are drawn? This can be a numeric (which will be applied to all sides) or it can be a collection of the paddings to be applied to the various sides: (left, top, right, bottom).

  • GridPen

    What Pen will be used to draw the grid lines of the cells?

In addition to these properties, there are some methods which add various decorations to the blocks:

  • Background(color=wx.BLUE, toColor=None, space=0)

    This gives the block a solid color background (or a gradient background if toColor is not None). If space is not 0, space pixels will be subtracted from all sides from the space available to the block.

  • Frame(pen=None, space=0)

    Draw a rectangle around the block in the given pen

  • Line(side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None)

    Draw a line on a given side of the block. If a pen is given, that is used to draw the line (and the other parameters are ignored), otherwise a solid line (or a gradient line is toColor is not None) of width pixels is drawn.

Can’t you just show me what these things do?

_images/listctrlprinter-formatting.png

Understanding the process

Use The Source Luke (at least until I write this part of the docs)

Other things to be aware of

  • A ListCtrlPrinter only works on ListCtrls that are in report view. It will ignore any ListCtrl that is in any other view.
  • You can set the left Padding of the ColumnHeader format and Row format to different values. This results in the column headers not lining up with the rows. This should be understood as a feature.
  • For reasons that are still not clear to me, images that come from BMP files will not print on some (most?) printers. Images that come from PNG and other formats work fine.
  • The ListCtrlPrinter is not designed to be general purpose reporting solution. There are no running totals, macro language, or ODBC data sources. It just prints ListCtrls.