Printing

 
Chapter 19 - Graphics with GDI+
bySimon Robinsonet al.
Wrox Press 2002
  

In this chapter we've focused so far entirely on drawing to the screen. However, at some point you will probably also want to be able to produce a hard copy of the data too. That's the topic of this section. We're going to extend the CapsEditor sample so that it is able to print preview and print the document that is being edited.

Unfortunately, we don't have enough space to go into too much detail about printing here, so the printing functionality we will implement will be very basic. Usually, if you are implementing the ability for an application to print data, you will add three items to the application's main File menu:

  • Page Setup allows the user to choose options such as which pages to print, which printer to use, etc.

  • Print Preview opens a new Form that displays a mock-up of what the printed copy should look

  • Print actually prints the document

In our case, to keep things simple, we won't implement a Page Setup menu option. Printing will only be possible using default settings. We will note, however, that, if you do want to implement Page Setup , then Microsoft has already written a page setup dialog class for you to use. It is the class System.Windows.Forms.PrintDialog . You will normally want to write an event handler that displays this form, and saves the settings chosen by the user.

In many ways printing is just the same as displaying to a screen. You will be supplied with a device context ( Graphics instance) and call all the usual display commands against that instance. Microsoft has written a number of classes to assist you in doing this; the two main ones that we need to use are System.Drawing.Printing.PrintDocument and System.Drawing.Printing.PrintPreviewDialog . These two classes handle the process of making sure that drawing instructions passed to a device context get appropriately handled for printing, leaving you to think about the logic of what to print where.

There are some important differences between printing/print previewing on the one hand, and displaying to the screen on the other hand. Printers cannot scroll instead they have pages. So you'll need to make sure you find a sensible way of dividing your document into pages, and draw each page as requested . Among other things that means calculating how much of your document will fit onto a single page, and therefore how many pages you'll need, and which page each part of the document needs to be written to.

Despite the above complications, the process of printing is quite simple. Programmatically, the steps you need to go through look roughly like this:

  • Printing You instantiate a PrintDocument object, and call its Print() method. This method will internally cause an event, PrintPage , to be raised to signal the printing of the first page. PrintPage takes a PrintPageEventArgs parameter, which supplies information concerning paper size and setup, as well as a Graphics object used for the drawing commands. You should therefore have written an event handler for this event, and have implemented this handler to print a page. This event handler should also set a Boolean property of the PrintPageEventArgs , HasMorePages , to either true or false to indicate whether there are more pages to be printed. The PrintDocument.Print() method will repeatedly raise the PrintPage event until it sees that HasMorePages has been set to false .

  • Print Previewing In this case, you instantiate both a PrintDocument object and a PrintPreviewDialog object. You attach the PrintDocument to the PrintPreviewDialog (using the property PrintPreviewDialog.Document ) and then call the dialog's ShowDialog() method. This method will modally display the dialog which turns out to be a standard Windows print preview form, and which displays pages of the document. Internally, the pages are displayed once again by repeatedly raising the PrintPage event until the HasMorePages property is false . There's no need to write a separate event handler for this; you can use the same event handler as used for printing each page since the drawing code ought to be identical in both cases (after all, whatever is print previewed ought to look identical to the printed version!).

Implementing Print and Print Preview

Now we've gone over the broad steps to be taken, let's see how this works in code terms. The code is downloadable as the PrintingCapsEdit project, and consists of the CapsEditor project, with the changes highlighted below made.

We start off by using the VS .NET design view to add two new items to the File menu: Print and Print Preview . We also use the properties window to name these items menuFilePrint and menuFilePrintPreview , and to set them to be disabled when the application starts up (we can't print anything until a document has been opened!). We arrange for these menu items to be enabled by adding the following code to the main form's LoadFile() method, which we recall is responsible for loading a file into the CapsEditor application:

 private void LoadFile(string FileName) {    StreamReader sr = new StreamReader(FileName);    string nextLine;    documentLines.Clear();    nLines = 0;    TextLineInformation nextLineInfo;    while ((nextLine = sr.ReadLine()) != null)    {       nextLineInfo = new TextLineInformation();       nextLineInfo.Text = nextLine;       documentLines.Add(nextLineInfo);       ++nLines;    }    sr.Close();   if (nLines > 0)     {     documentHasData = true;     menuFilePrint.Enabled = true;     menuFilePrintPreview.Enabled = true;     }     else     {     documentHasData = false;     menuFilePrint.Enabled = false;     menuFilePrintPreview.Enabled = false;     }   CalculateLineWidths();    CalculateDocumentSize();    this.Text = standardTitle + " - " + FileName;    this.Invalidate(); } 

The highlighted code above is the new code we have added to this method. Next we add a member field to the Form1 class:

 public class Form1 : System.Windows.Forms.Form    {   private int pagesPrinted = 0;   

This field will be used to indicate which page we are currently printing. We are making it a member field, since we will need to remember this information between calls to the PrintPage event handler.

Next, the event handlers for when the user selects the Print or Print Preview menu options:

   private void menuFilePrintPreview_Click(object sender, System.EventArgs e)     {     this.pagesPrinted = 0;     PrintPreviewDialog ppd = new PrintPreviewDialog();     PrintDocument pd = new PrintDocument();     pd.PrintPage += new PrintPageEventHandler     (this.pd_PrintPage);     ppd.Document = pd;     ppd.ShowDialog();     }     private void menuFilePrint_Click(object sender, System.EventArgs e)     {     this.pagesPrinted = 0;     PrintDocument pd = new PrintDocument();     pd.PrintPage += new PrintPageEventHandler     (this.pd_PrintPage);     pd.Print();     }   

We've already explained the broad procedure involved in printing, and we can see that these event handlers are simply implementing that procedure. In both cases we are instantiating a PrintDocument object and attaching an event handler to its PrintPage event. For the case of printing, we call PrintDocument.Print() , while for print previewing, we attach the PrintDocument object to a PrintPreviewDialog , and call the preview dialog object's ShowDialog() method. The real work is going to be done in that event handler to the PrintPage event and this is what that handler looks like:

   private void pd_PrintPage(object sender, PrintPageEventArgs e)     {     float yPos = 0;     float leftMargin = e.MarginBounds.Left;     float topMargin = e.MarginBounds.Top;     string line = null;     // Calculate the number of lines per page.     int linesPerPage = (int)(e.MarginBounds.Height /     mainFont.GetHeight(e.Graphics));     int lineNo = this.pagesPrinted * linesPerPage;     // Print each line of the file.     int count = 0;     while(count < linesPerPage && lineNo < this.nLines)     {     line = ((TextLineInformation)this.documentLines[lineNo]).Text;     yPos = topMargin + (count * mainFont.GetHeight(e.Graphics));     e.Graphics.DrawString(line, mainFont, Brushes.Blue,     leftMargin, yPos, new StringFormat());     lineNo++;     count++;     }     // If more lines exist, print another page.     if(this.nLines > lineNo)     e.HasMorePages = true;     else     e.HasMorePages = false;     pagesPrinted++;     }   

After declaring a couple of local variables , the first thing we do is work out how many lines of text can be displayed on one page which will be the height of a page divided by the height of a line and rounded down. The height of the page can be obtained from the PrintPageEventArgs.MarginBounds property. This property is a RectangleF struct that has been initialized to give the bounds of the page. The height of a line is obtained from the Form1.mainFont field, which we recall from the CapsEditor sample is the font used for displaying the text. There is no reason here for not using the same font for printing too. Note that for the PrintingCapsEditor sample, the number of lines per page is always the same, so we arguably could have cached the value the first time we calculated it. However, the calculation isn't too hard, and in a more sophisticated application the value might change, so it's not bad practice to recalculate it every time we print a page.

We also initialize a variable called lineNo . This gives the zero-based index of the line of the document that will be the first line of this page. This information is important because in principle, the pd_PrintPage() method could have been called to print any page, not just the first page. lineNo is computed as the number of lines per page times the number of pages that have so far been printed.

Next we run through a loop, printing each line. This loop will terminate either when we find that we have printed all the lines of text in the document, or when we find that we have printed all the lines that will fit on this page whichever condition occurs first. Finally, we check whether there is any more of the document to be printed, and set the HasMorePages property of our PrintPageEventArgs accordingly , and also increment the pagesPrinted field, so that we know to print the correct page the next time the PrintPage event handler is invoked.

One point to note about this event handler is that we do not worry about where the drawing commands are being sent. We simply use the Graphics object that was supplied with the PrintPageEventArgs . The PrintDocument class that Microsoft has written will internally take care of making sure that, if we are printing, the Graphics object will have been hooked up to the printer, while if we are print previewing then the Graphics object will have been hooked up to the print preview form on the screen.

Finally, we need to ensure the System.Drawing.Printing namespace is searched for type definitions:

  using  System;  using  System.Drawing;   using System.Drawing.Printing;    using  System.Collections;  using  System.ComponentModel;  using  System.Windows.Forms;  using  System.Data;  using  System.IO; 

All that remains is to compile the project and check that the code works. We can't really show screenshots of a printed document(!) but this is what happens if you run CapsEdit , load a text document (as before, we've picked the C# source file for the project), and select Print Preview :

click to expand

In the screenshot, we have scrolled through to page 5 of the document, and set the preview to display normal size. The PrintPreviewDialog has supplied quite a lot of features for us, as can be seen from the toolbar at the top of the form. The options available include actually printing the document, zooming in or out, and displaying two, three, four or six pages together. These options are all fully functional, without our needing to do any work. For example, if we change the zoom to auto and click to display four pages (third toolbar button from the right), we get this.

click to expand
  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net