Printing


So far, the chapter has focused exclusively 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. That’s the topic of this section. You’re going to extend the CapsEditor sample so that it is able to print preview and print the document that is being edited.

Unfortunately, there’s not enough space to go into too much detail about printing here, so the printing functionality you will implement is very basic. Usually, if you are implementing the ability for an application to print data, you will need to add three items to the application’s main File menu:

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

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

  • Print, which prints the document.

In this case, to keep things simple, you won’t implement a Page Setup menu option. Printing will only be possible using default settings. Note, however, that, if you do want to implement Page Setup, Microsoft has already written a page setup dialog class for you to use: 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 you 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 are handled appropriately for printing, leaving you to think about the logic of what to print where.

Some important differences exist between printing or print previewing on the one hand, and displaying to the screen on the other hand. Printers cannot scroll; instead they turn out pages. So, you’ll need to make sure that 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 these 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 signals the PrintPage event to print 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 modally displays 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 because 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 that this process has been outlined in broad strokes, in this section you see how this works in code terms. You can download the code as the PrintingCapsEdit project at www.wrox.com; it consists of the CapsEditor project with the changes highlighted in the following snippet.

You begin by using the Visual Studio 2005 design view to add two new items to the File menu: Print and Print Preview. You also use the properties window to name these items menuFilePrint and menuFilePrintPreview, and to set them to be disabled when the application starts up (you can’t print anything until a document has been opened!). You arrange for these menu items to be enabled by adding the following code to the main form’s LoadFile() method, which 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 is the new code added to this method. Next, you 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 you are currently printing. You are making it a member field, because you will need to remember this information between calls to the PrintPage event handler.

Next, the event handlers that handle the selection of 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(); } 

You’ve already seen the steps involved in printing, and you can see that these event handlers are simply implementing that procedure. In both cases, you are instantiating a PrintDocument object and attaching an event handler to its PrintPage event. In the case of printing, you call PrintDocument.Print(), whereas for print previewing, you attach the PrintDocument object to a PrintPreviewDialog and call the preview dialog box object’s ShowDialog() method. The real work to the PrintPage event is done in the event handler. Here is what this 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 you 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 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 you arguably could have cached the value the first time you 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 you print a page.

You 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, you run through a loop, printing each line. This loop will terminate either when you find that you have printed all the lines of text in the document, or when you find that you have printed all the lines that will fit on this page, whichever condition occurs first. Finally, you check whether there is any more of the document to be printed, and set the HasMorePages property of your PrintPageEventArgs accordingly, and also increment the pagesPrinted field, so that you 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 you do not worry about where the drawing commands are being sent. You 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 you are printing, the Graphics object will have been hooked up to the printer; if you are print previewing, the Graphics object will have been hooked up to the print preview form on the screen.

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

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

All that remains is to compile the project and check that the code works. Figure 30-19 shows what happens when you run CapsEdit, load a text document (as before, you’ve picked the C# source file for the project), and select Print Preview.

image from book
Figure 30-19

In Figure 30-19, the document is scrolled to page 5 and the preview is set to display normal size. The PrintPreviewDialog has supplied quite a lot of features, as you can see by looking at the toolbar at the top of the form. The options available include printing the document, zooming in or out, and displaying two, three, four, or six pages together. These options are all fully functional, without your having to do any work. Figure 30-20 shows the result of changing the zoom to auto and clicking to display four pages (third toolbar button from the right).

image from book
Figure 30-20




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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