Chapter 21. Adding Printing Support for Arbitrary Data

 

The boldness of asking deep questions may require unforeseen flexibility if we are to accept the answers.

 
 --Brian Greene

One of the more common and important tasks in a Windows application is the ability to print text or graphics. Printing was somewhat tricky to implement in the days prior to .NET, but now there is a versatile framework to support it within the System.Drawing.Printing namespace. The majority of the print mechanism is within the PrintDocument class, which represents a component that sends output to a printer. This class is very modular, so it allows you to implement either simple or complex printing logic and execute it using this class alone. Other classes exist to support printer configuration and page setup properties such as orientation. This chapter is all about using the managed mechanisms within the .NET Class Framework to implement printing support within applications. You should have a basic familiarity with the Graphics class within the System.Drawing namespace.

Printing Regular Text

The first thing that we must implement is the actual printing logic, which is done by linking into events on PrintDocument. There is a method on PrintDocument called Print() which, you guessed it, prints the document. When this method is called, a BeginPrint event is fired, followed by a PrintPage event for each page, and finally stopping with an EndPrint event. You do not really have to do much with the begin and end events; the core logic exists in the PrintPage event. This event is passed a PrintPageEventArgs parameter that contains a property called HasMorePages. If this property is set to true, a new page is created and the PrintPage event is raised again when the event handler returns.

The pseudologic for the PrintPage event handler is basically: Print the page content using the page setting information provided, using the Graphics context provided. Determine if more pages are needed to completely print all the content for the document. If yes, set HasMorePages to true; otherwise set it to false.

The following code shows how to instantiate a PrintDocument, wire up to the PrintPage event handler, and start printing.

PrintDocument printDocument = new PrintDocument();
printDocument.PrintPage += new PrintPageEventHandler(printDocument_PrintPage);
printDocument.Print();

The following code describes the simplest implementation of the PrintPage event handler, assuming that no additional pages are needed.

private void printDocument_PrintPage(Object sender, PrintPageEventArgs e)
{
    string outputText = "Game Engine Toolset Development rocks!";
    Font printFont = new Font("Verdana",
                              9.75F,
                              FontStyle.Regular,
                              GraphicsUnit.Point,
                              ((byte)(0)));

    e.Graphics.DrawString(outputText,
                          printFont,
                          Brushes.Black,
                          0,
                          0);
}

Complex printing logic that you wish to reuse across multiple places requires that you inherit from PrintDocument, handling the PrintPage event by overriding the OnPrintPage method instead of using the event handler. The following code shows a sample implementation of a PrintDocument that correctly handles text printing that spans multiple pages with varying font and page settings.

public class SimplePrintDocument : PrintDocument
{
    private StringReader inputStream = null;
    private string bufferOverflow = null;
    private Font printFont = null;

    public SimplePrintDocument(StringReader inputStream, Font printFont)
        : base()
    {
        this.inputStream = inputStream;
        this.printFont = printFont;
    }

    protected override void OnBeginPrint(PrintEventArgs e)
    {
        base.OnBeginPrint(e);
        bufferOverflow = null;
    }

    protected override void OnPrintPage(PrintPageEventArgs e)
    {
        base.OnPrintPage(e);

        // Figure out how many lines can fit within the page boundaries
        float linesPerPage = e.MarginBounds.Height /
                             printFont.GetHeight(e.Graphics);

        int lineCount = 0;

        // Deal with any remaining overflow lines from a previous page first
        while (lineCount < linesPerPage && bufferOverflow != null)
        {
            float positionY = e.MarginBounds.Top +
                              (lineCount * printFont.GetHeight(e.Graphics));
            lineCount += PrintLine(e, bufferOverflow, positionY);
        }

        // Now handle the current line buffer
        string line = null;
        while (lineCount < linesPerPage &&
                  ((line = inputStream.ReadLine()) != null))
        {
        float positionY = e.MarginBounds.Top +
                          (lineCount * printFont.GetHeight(e.Graphics));
        lineCount += PrintLine(e, line, positionY);
    }

    // Print a new page if there are more lines to print
    if (line != null)
        e.HasMorePages = true;
    else
        e.HasMorePages = false;
}

private int PrintLine(PrintPageEventArgs e, string text, float positionY)
{
    RectangleF rectangle = new RectangleF(e.PageSettings.Margins.Left,
                                          positionY,
                                          e.MarginBounds.Width,
                                          e.MarginBounds.Height);

    int lines;
    int characters;

    StringFormat format = new StringFormat();

    e.Graphics.MeasureString(text,
                             printFont,
                             rectangle.Size,
                             format,
                             out characters,
                             out lines);

    // Total text will not fit on page; bump to overflow buffer for next page
    if (characters < text.Length)
    {
        bufferOverflow = text.Substring(characters);
    }
    else
    {
        bufferOverflow = null;
    }

        e.Graphics.DrawString(text,
                              printFont,
                              Brushes.Black,
                              rectangle,
                              format);

         // Handle empty lines
         lines = lines == 0 ? 1 : lines;
         return lines;
      }
}

Using the new SimplePrintDocument class is easy; instantiate it as you did with PrintDocument and call the Print() method!

Supporting Printer Selection

We currently have the logic for printing support implemented, so the next logical step is to provide the ability to select a printer using the standard Windows Print dialog. Right now, you are simply calling the print method on the document, but in a real world application, you let the user select the printer she wants to use and also support the ability to cancel printing. Using the PrintDialog class, we can provide this functionality to users. Attach the print document to the Document property of the dialog and show the dialog as normal. If the dialog returns successfully, call the print method of the document. The following code shows a sample implementation of printer selection.

private void PrintButton_Click(object sender, EventArgs e)
{
    using (StringReader inputText = new StringReader(PrintTextField.Text))
    {
        SimplePrintDocument printDocument = new SimplePrintDocument(inputText,
                                                                    printFont);

        PrintDialog printDialog = new PrintDialog();
        printDialog.Document = printDocument;

        if (printDialog.ShowDialog() == DialogResult.OK)
            printDocument.Print();
    }
}

Figure 21.1 shows the printer selection dialog in action.

Printer selection dialog in action.

Figure 21.1. Printer selection dialog in action.

Supporting Page Setup

Another common print feature provided by real-world applications is the ability to choose page settings like the orientation of the paper or the margin sizes. This can be done with the PageSetupDialog class and a stored instance of the PageSettings class, as shown with the following code.

private void PageSetupButton_Click(object sender, EventArgs e)
{
    PageSetupDialog pageSetupDialog = new PageSetupDialog();

    if (cachedSettings == null)
        cachedSettings = new PageSettings();

    pageSetupDialog.PageSettings = cachedSettings;
    pageSetupDialog.ShowDialog();
}

You can now alter the printing logic to set the page settings to our cached instance, as shown with the following code.

private void PrintButton_Click(object sender, EventArgs e)
{
    using (StringReader inputText = new StringReader(PrintTextField.Text))
    {
         SimplePrintDocument printDocument = new SimplePrintDocument(inputText,
                                                                     printFont);

         if (cachedSettings != null)
             printDocument.DefaultPageSettings = cachedSettings;

         PrintDialog printDialog = new PrintDialog();
         printDialog.Document = printDocument;

         if (printDialog.ShowDialog() == DialogResult.OK)
             printDocument.Print();
     }
}

Figure 21.2 shows the Page Setup dialog in action.

Page Setup dialog in action.

Figure 21.2. Page Setup dialog in action.

Supporting Print Preview

The last common print feature is the ability to preview a document before actually printing it. This is done with the PrintPreviewDialog class. Simply attach your print document to the Document property of the dialog and show the dialog as usual. The following code shows how to do this.

private void PrintPreviewButton_Click(object sender, EventArgs e)
{
    using (StringReader inputText = new StringReader(PrintTextField.Text))
    {
        SimplePrintDocument printDocument = new SimplePrintDocument(inputText,
                                                                    printFont);

        if (cachedSettings != null)
            printDocument.DefaultPageSettings = cachedSettings;

        PrintPreviewDialog printPreviewDialog = new PrintPreviewDialog();
        printPreviewDialog.Document = printDocument;
        printPreviewDialog.ShowDialog();
    }
}

Figure 21.3 shows the print preview dialog in action.

Print preview dialog in action.

Figure 21.3. Print preview dialog in action.

Conclusion

This chapter covered the full implementation of a PrintDocument class that can print arbitrary text with varying fonts and page settings. The user can select which printer to use and can modify page properties before printing. In addition to configuration, the user can also bring up a print preview dialog that shows the document as it would print out before actually committing himself to a print job.

Although this chapter did not cover printing graphics, remember that the PrintPage event handler is passed a Graphics context that functions like any other context. You can call methods like FillRectangle() or DrawEllipse() on it and achieve the desired effect. It is a little trickier when you start introducing graphics, because you need to implement some form of flow layout to determine the lines per page and how you position your content when printing.

The Companion Web site contains the full source code from this chapter, along with an example utilizing the custom print logic. Figure 21.4 shows the interface of the example, which is simply a front-end to the code discussed throughout this chapter.

Screenshot of the Companion Web site example.

Figure 21.4. Screenshot of the Companion Web site example.

 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.198.94