Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

All-in-one printing utility for user controls

0.00/5 (No votes)
11 Jul 2007 1  
Prints the content of user controls and provides printing settings and preview

Screenshot - PrintUtilityForUserControls.jpg

Introduction

This printing utility is a combination of two efforts. It has the ability to print the content of user controls and it is, moreover, an attempt to create an "all-in-one" printing utility. The latter is an integration into a single control of what is otherwise spread across several standard dialogs for printer properties, page setup options and print previewing.

Background

Grateful use is made of the following article here on CodeProject, authored by Nader Elshehabi: A component that prints any control... I needed to print the content of a treeview, amongst other things, and the above solution proved to do a fine job. I did not extend very much on it, but instead wrapped it with a printing utility that integrates the facilities of the System.Drawing.Printing namespace into a PropertyGrid.

Another incentive for this control is the variety of printing dialogs out there. If you look at the printing tools supplied with the purchase of an average printer, their appearance, non-standardisation and staggering complexity are, exceptions aside, overwhelming, intimidating and fundamentally scary. They definitely do not help people to understand their printers.

This is not an approach with a juicy UI-appeal, but that was not intended. I'm somewhat devoted to the power of PropertyGrid leveraged by the System.ComponentModel namespace. I believe that effective designer grids can even appeal to end users, provided that added value is somewhere in the equation.

Using the code

The solution contains two projects:

  • Codebasement.PrintControl: the printing utility user control and supporting classes
  • Codebasement.PrintControl.Test: a simple test form

The printing utility project contains several classes, which will be commented on below.

PrintDocumentControl is the actual user control as illustrated in the partial image above. It contains tool strips for printing and preview manipulation, a property grid for the various settings and PrintPreviewControl for displaying previews. This latter control, by the way, has to be used programmatically. It will not do its job if used as a design-time drag and drop control. PrintDocumentControl can be used as-is and has a simple constructor that only needs to be provided with the target user control to print.

The control checks whether the Print Spooler service is running and, if so, also checks whether there is a printer installed. It wires the various elements of the control together and contains the methods that do the actual work, notably ShowPreview() and PrintNow(). I could have splattered this functionality across other classes, but decided to take the simple route and take all that up in the control itself. Since the control is intended to be used singularly in a dialog, there is a PrintDocumentForm in the project that already contains the control.

The ShowPreview() method is described below. It utilizes the classes discussed further on, assigning properties to PrintDocumentComponent -- i.e. the _printDocument member -- to be previewed. It creates a PreviewPrintController, which is not to be confused with the PrintPreviewControl user control, and assigns this controller to the document. Finally, it hands the document to the preview user control.

The PreviewToolStrip, incidentally, has several functions that you would expect:

  • (Re)Show preview
  • Zoom in/out
  • Fit to page
  • Set zoom factor
  • Step through pages
  • Multi-page view
private void ShowPreview()
{
    Cursor = Cursors.WaitCursor;

    //rig up a new PrintPreviewControl

    NewPrintPreviewControl();

    //set several properties of the PrintDocument

    _printDocument.DocumentName = _printDocumentSettings.HeaderText;
    _printDocument.DefaultPageSettings = _printDocumentSettings.PageSettings;
    _printDocument.PrinterSettings = _printDocumentSettings.PrinterSettings;
    _printDocument.OriginAtMargins = _printDocumentSettings.OriginAtMargins;

    //create a PreviewPrintController

    PreviewPrintController pc = new PreviewPrintController();
    pc.UseAntiAlias = true;

    //assign the PreviewPrintController to the PrintDocument

    _printDocument.PrintController = pc;

    //assign the document to the PrintPreviewControl

    _printPreviewControl.Document = _printDocument;
    _printPreviewControl.StartPage = 0;
    _printPreviewControl.UseAntiAlias = true;

    //set page counter

    _pageCounter = 1;

    //set initial values for the preview toolstrip

    SetPreviewToolStrip();

    Cursor = Cursors.Default;
}

The PrintNow() method is similar. It also assigns PrinterSettings and PageSettings assembled in PrintDocumentSettings to PrintDocumentComponent. Then, using PrintControllerWithStatusDialog as a print controller as before, the actual Print() method is ultimately called upon.

private void PrintNow()
{
    DialogResult result;
    if (_printDocumentSettings.PrintToFile)
    {
        result =
            MessageBox.Show(
            "The document " + _printDocument.DocumentName +
            " will be saved to file " + 
            _printDocumentSettings.FileName +
            ". Continue?",
            "Print to file...",
            MessageBoxButtons.YesNo,
            MessageBoxIcon.Question,
            MessageBoxDefaultButton.Button2,
            0);

        if (result == DialogResult.Yes)
        {
            PrintToFile();
            return;
        }
    }

    result =
        MessageBox.Show(
        "The document " + _printDocument.DocumentName + 
        " from page " + _printDocumentSettings.FromPage +
        " to page " + _printDocumentSettings.ToPage +
        " will be sent to printer " + 
        _printDocumentSettings.SelectedPrinter + 
        ". Continue?",
        "Print to printer...",
        MessageBoxButtons.YesNo,
        MessageBoxIcon.Question,
        MessageBoxDefaultButton.Button2,
        0);

    if (result == DialogResult.Yes)
    {
        _printDocument.PrinterSettings = 
            _printDocumentSettings.PrinterSettings;
        _printDocumentSettings.PageSettings.PrinterSettings = 
            _printDocument.PrinterSettings;
        _printDocument.DefaultPageSettings = 
            _printDocumentSettings.PageSettings;

        StandardPrintController spc = new StandardPrintController();
                
        _printDocument.PrintController = 
            new PrintControllerWithStatusDialog(spc,
            "Printing "+_printDocument.DocumentName + " to " + 
            _printDocumentSettings.SelectedPrinter);

        _printDocument.Print();
    }
}

PrintDocumentSettings is a class that supplies a number of properties taken from the System.Drawing.Printing namespace. The idea behind it is to simplify access to this namespace by bundling the most immediately relevant contents, while on the other hand making this content "PropertyGrid-aware." This is best illustrated by an example: the SelectedPinter property in the snippet shown below gets/sets a member field and adjusts a PrinterSettings member instance in which the printer settings are collected for use when the ShowPreview() or PrintNow() methods are called.

The attributes used are mostly standard, but they do provide added value for the PropertyGrid used. I will not address the application of type converters here in-depth. As an example, the PrinterSelectionConverter used below is an included custom type converter class that provides combo fill for the property when used in the grid to enable selecting a printer. So the return type is simply a string, but the type converter provides a little magic under the cover.

[Category(catPrinter)]
[TypeConverter(typeof(PrinterSelectionConverter))]
[RefreshProperties(RefreshProperties.All)]
[Description("Choice for the selected printer.")]
[DisplayName("Selected printer")]
public string SelectedPrinter
{
    get 
    { 
        return _selectedPrinter; 
    }
    set
    {
        _selectedPrinter = value;
        _printerSettings.PrinterName = value;
    }
}

Another example is the PaperKind property. The setter parses the input value to a string value of the related enum. The PaperSize of the _pageSettings member is then assigned to a PaperSize instance with this PaperKind, and otherwise to a default by the custom SetPaperSize method. In the getter, an inline validation of the PaperKind is carried out to ensure that the value corresponds to a valid value of the paper size collection supported by the selected printer. This collection is again provided by a type converter. In this case, it's PaperKindSelectionConverter. Simple code, essentially, but it results in powerful functionality by combining programmatic and declarative elements.

[Category(catPaper)]
[DefaultValue("A4")]
[Description("Paper size kind for selected printer.")]
[DisplayName("Paper size kind")]
[TypeConverter(typeof(PaperKindSelectionConverter))]
public string PaperKind
{
    get
    {
        return ValidatePaperSize(_paperKind).ToString();
    }
    set
    {
        if (value != null)
        {
            _paperKind = 
                (PaperKind) Enum.Parse(typeof (PaperKind), 
                value.ToString());
            _pageSettings.PaperSize = SetPaperSize(_paperKind);
        }
    }
}

The document settings further provide several options for printing header and footer info along with the control content. Text options, page numbering and a datetime can be included. Alignment, font and color preferences can also be set.

PrintDocumentComponent is the derivative of the PrintDocument base class taken from the article mentioned above. I will therefore not go into detail here about its contents. The fundamental ingredients is the CalculateSize method, which provides sizing of the user control content that is to be printed. In here, several controls are given precise size calculations. Otherwise, GetPreferredSize is the default. The other crucial part is the OnPrintPage event handler. This takes care of the actual printing of bitmaps per page, taking an overlap area into account. Optionally, it stretches the target control to print and sizes it to the required dimensions. I mixed in the printing of header and footer texts as a minor extension.

Points of interest

The user controls covered currently in the PrintDocumentComponent class are limited to only a few specific ones, with a default sizing for any other. It is important that the target user control support the DrawToBitmap method. For this reason, not all user controls can be printed with the above utility. See the original article -- and specifically also the related message threads -- referenced above in the Background paragraph for more details concerning this PrintDocument derivative.

Printing the content of specific controls might require some addition to the PrintDocumentComponent.CalculateSize method, whereby you supply or calculate the height and width of the control content. Also, you might want to adjust the behaviour of expansions and the like, as the content printing is initially a WYSIWYG view. For a tree view, no expansion of nodes is done within CalculateSize. By contrast, for the printing of a PropertyGrid's contents, the grid items are expanded there. It illustrates the difference and adjusts it to your needs. This behaviour could also be integrated into the PrintDocumentSettings class as a selectable option.

My experience in using the System.Drawing.Printing namespace is that it's not an immediate friend. This namespace could be greatly improved to a true-blood object model, abstracting the current diversity and complexity. Who knows what may happen in the.NET 4.X future or so.

Known issues and improvement points

  • I tried cloning the target user control to avoid having to optionally resize the target control -- as is done in the solution mentioned above that I used for this purpose -- but that didn't help. Fiddling around with the original controls is not a very elegant approach, so that is definitely a point still to be improved.
  • The page range selection does not work adequately; i.e. when selecting a smaller range, the printer settings don't pick that up. I have no clue as yet why that doesn't work.
  • The property grid might be somewhat slack in performance, depending on circumstances. The problem here is that I use some inline validation in a few getters, as well as an attribute like RefreshProperties, which is very useful but tends to downgrade performance.
  • The date format optionally printed is currently fixed to a format with an ISO-standard character-based month abbreviation. The idea is that it cannot easily be misunderstood. Of course, as a European I'm more inclined to find logic in a day-month-year sequence. ;-)
  • I put some effort into getting the solution "resharper-green," but this will depend on the Resharper build used. It is also, to a large extent, static-code-analysis-compliant.

History

  • 15 June, 2007 -- Original version posted
  • 11 July, 2007 -- Article edited and moved to the main CodeProject.com article base

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here