CSCI E-143 CertPrep:    Printing   (Kalani,  chapter 11)

Saturday,  Jan.  8, 2005

This Saturday we cover Kalani's chapter 11,   "Printing".   It's really pretty simple,  and the resemblance to the graphics stuff from chapter 1 is striking.   (You remember chapter 1?)

Aside from Kalani,  it is surprisingly difficult to find any documentation about printing other than the on-line help.   I couldn't find anything in the books listed on the  references  page.


Basic Printing

Here are the steps necessary to print anything at all: 

Now,  pay attention:  you do  not  place the printing code directly in the button's  Click  event,  nor do you override the  PrintDocument's  Print()  method.   The printing code goes into the  PrintPage  event for the  PrintDocument.   This event is then raised by the  printDocument.Print()  method call.

Here is an example of the  PrintPage  event:           (Kalani,   p.   733)

  private void printDocument1_PrintPage
     (object sender, System.Drawing.Printing.PrintPageEventArgs e)
  {
     Font font = new Font("Arial", 24);

     float x = e.MarginBounds.Left;
     float y = e.MarginBounds.Top;

     e.Graphics.DrawString("Hello, Printer!", font, Brushes.Black, x, y);

     e.HasMorePages = false;  //not really necessary: it's the default
  }

What would be wrong with saying  this.Font  for  font  in this example?   You must not confuse Graphics objects for screen display  (which is what  this.Font  is)  with Graphics objects for printing,  even though you may handle them the same way—and even though they may work,  at least sometimes,  as this example might,  though it certainly wouldn't give 24-point type.

You will remember that the last two arguments to the  DrawString  function are the  x  and  y  coordinates of the location on the page to start printing.   To print more than one line,  you increment each of these as needed after printing each line.  


Some arguments controlling printing

The  PrintPageEventArgs  argument of the  PrintPageEvent  has the following properties:

The  PrintEventArgs  argument of the  BeginPrint  and  EndPrint  events has almost no useful properties.   Be sure to distinguish  PrintEventArgs  from  PrintPageEventArgs,  just described.   See the  BeginPrint  and  EndPrint  events under  "Events Controlling Printing",  below.

PageSettings  properties:        (Kalani,  p.   745)

PrinterSettings  properties  (well,  some of them,  anyway):

Where to reset  PageSettings  andc PrinterSettings

There are two places you are expected to change these settings:

  1. System dialog boxes,  which you can invoke from your code: Both of these dialog boxes are described below,  under  "Dialogs and Components controlling printing".
  2. Your code.   The expected place to do this is in the  QueryPageSettings  event.   Changes made here to the  PageSettings  properties will be effective on the next page printed by the  PrintPage  event.

    But there are a couple of glitches:

Boundaries controlling printing

There are two boundaries of interest:  PageBounds  and  MarginBounds.   The first set describes the size of the paper,  and a rectangle drawn at these bounds will be at the paper's edge.   The  MarginBounds  describe the printable area—that is,  the area with the margins as specified in a  PageSetupDialog.   To use these margins in your printing,  you must use them when initializing the X and Y coordinates describing your print position.   If you ignore them,  you will print outside your stated margins.

The sample code above uses the margin bounds for positioning the printing.   If instead of giving the position as  (x,y),  the  e.Graphics.DrawString  call had specified  (0,0),  the text would have appeared in the top left corner of the paper,  regardless of any specified margins.   In other words,  you are not  forced  to print only with the area within the   MarginBounds.   Furthermore,  your print may very well fall outside the bottom margin if you print too many lines on a page.   The system doesn't prevent  this,  either.

Printing Multiple Pages

If you are printing,  say,  more lines than will fit on one page,  you must set the  HasMorePages  property of the  Graphics  object to  true.   This will cause the entire  PrintPageEvent  to be called again.   It is up to you,  the programmer,  to assure that there is no endless printing loop.   You must take care that the counters controlling the printing,  such as a line counter,  are not re-initialized in the call to the  PrintPageEvent  as they may never get to their proper limit,  and you may print more lines than you want.

There is no automatic carryover from one page to the next.   If the first page runs over the lower edge,  it will not appear on the next page—unless you go to the trouble of making it do so.

Properties of the  Graphics  object

There are in fact several properties of this object,  but few are of interest to Mr. Kalani.   Here are some things he didn't say about one of them:       (Help: "SmoothingMode enumeration")


Events occurring during printing

PrintPage.   This event does the work of printing.   It has already been discussed.

BeginPrint.   This event occurs once per print job,  before the first page prints.   Here's an example:

private void printDocument1_BeginPrint
  (object sender, System.Drawing.Printing.PrintEventArgs e)
{
  Debug.WriteLine("\nThis message is from the BeginPrint event");
}
Be sure to note the second argument passed in:  PrintEventArgs,  not  PrintPageEventArgs.   You cannot set page setup properties here,  because  PrintEventArgs  doesn't give you a  PageSettings  object.   In fact,  you don't get any useful arguments at this call;  about all it's good for is printing a separator or job-label page.

EndPrint.   This event occurs once per print job,  after the last page prints.   Here's an example:

private void printDocument1_EndPrint
  (object sender, System.Drawing.Printing.PrintEventArgs e)
{
  Debug.WriteLine("\nThis message is from the EndPrint event: "
    + pageCount + " pages printed.\n");    
}
This event doesn't pass you any useful arguments,  either.

QueryPageSettings.   This event occurs just  before each  PrintPage  event.   It allows resetting of properties in  QueryPageSettingsEventArgs.PageSettings  for the next page to be printed.

It is only this  QueryPageSettings  event that passes you the useful  QueryPageSettingsEventArgs  argument.   This event and the  PrintPage  event both pass you a  PageSettings  property,  but you can only  set  this property when it comes from  QueryPageSettingsEventArgs.

If you want to cancel a print job from code  (for instance,  if a page limit has been exceeded),  you may set the  Cancel  switch from  either  the  PrintPage  event or this  QueryPageSettings  event.

Here's an example  (not involving cancellation):

    private void printDocument1_QueryPageSettings
      (object sender, System.Drawing.Printing.QueryPageSettingsEventArgs e)
    {      
      Debug.WriteLine("\nAt entry to the QueryPageSettings event for page #" 
        + (printCalls+1) +": "
        + "PageSettings.Landscape = " + e.PageSettings.Landscape.ToString()
        + "; PageSettings.PaperSize = " + e.PageSettings.PaperSize.ToString());

      //This next line sets the paper orientation to "Portrait",
      //no matter what the user specified.

      e.PageSettings.Landscape = false;

      //This next line sets the paper size to "Letter", 
      //no matter what the user specified.

      e.PageSettings.PaperSize = new PaperSize("Letter", 850, 1100);

      Debug.WriteLine("At exit from the QueryPageSettings event for page #" 
        + (printCalls+1) + ": "
        + "PageSettings.Landscape = " + e.PageSettings.Landscape.ToString()
        + "; PageSettings.PaperSize = " + e.PageSettings.PaperSize.ToString()
        + "\n");
    }
Here is the output from the first occurrence of this event.   You will notice that the user's specifications  (landscape orientation,  legal paper)  are reset:
At entry to the QueryPageSettings event for page #1: 
  PageSettings.Landscape = True;   
  PageSettings.PaperSize = 
    [PaperSize Legal (8.5 x 14 in.)  Kind=Legal Height=1400 Width=850]
At exit from the QueryPageSettings event for page #1: 
  PageSettings.Landscape = False;   
  PageSettings.PaperSize = 
    [PaperSize Letter Kind=Custom Height=1100 Width=850]
Here is the output from the second occurrence of this event.   You will notice that the user's specifications  (landscape orientation,  legal paper)  are not mentioned,  but that the settings made by the first call to this event are now passed in:
At entry to the QueryPageSettings event for page #2: 
  PageSettings.Landscape = False; 
  PageSettings.PaperSize = 
    [PaperSize Letter Kind=Custom Height=1100 Width=850]
At exit from the QueryPageSettings event for page #2: 
  PageSettings.Landscape = False; 
  PageSettings.PaperSize = 
    [PaperSize Letter Kind=Custom Height=1100 Width=850]

But if you call for another entire print run,  the user's  original  specifications will be presented to the  QueryPageSettingsEventArgs.PageSettings  event the first time the event occurs.

Finally,  the user's page and printer specifications  (as set here)  are  not  preserved across separate start-ups of the program that does the printing.


Dialogs and Components controlling printing

All of these dialogs and components may be created by dragging an object from the IDE's Toolbox onto your Form.   However, the code needed to create each one is so simple that you may find it easier to create them in code.

The sample code in the following descriptions assumes that you have created a  PrintDocument  object named  printDocument1.

PrintDialog

This allows settings for the printer:

  • Printer name
  • A printer properties button  (upper right),  which when pressed will bring up screens allowing you to set
    • orientation
    • printing quality
  • Whether to print to a file
  • Print range (pages)
  • Number of copies
  • Whether to collate
  • A Help button

Most of these options may be enabled and disabled by settings made in code.   (Some of the options shown in this image are in fact hidden or disabled by default.)

[Picture of the PrintDialog]

(Picture: Kalani, p. 755)

To use the  PrintDialog,  you must create a  PrinterSettings  object:    (Kalani, p. 753)
    PrintDialog pd = new PrintDialog();
    pd.PrinterSettings = new PrinterSettings();
    if (pd.ShowDialog() == DialogResult.OK)
    {
      printDocument1.PrinterSettings = pd.PrinterSettings;
    }
    //Otherwise the default printer settings are used.
Creating a  new  copy of  PrinterSettings  this way guarantees that the old settings will be lost each time you bring up the dialog.   If you want to preserve your settings,  you must do so by retaining the old copy of  PrinterSettings.

PageSetupDialog

This allows settings for the paper:

  • Paper Size and source
  • Orientation
  • Margins
  • Printer settings

To use this dialog,  You must create a  PageSettings  object for it,  or you will get an Exception.   See code below.

The dialog has four boolean properties enabling and disabling its various panes: 

  • AllowPaper
  • AllowOrientation
  • AllowMargins
  • AllowPrinter.   This turns on the button in the lower right corner.   The  "Printer"  button causes the display of  only the top pane  of the  PrintDialog  of the preceding example.
[Picture of the PageSetupDialog]

(Picture: Kalani, p. 755)

To get this  Printer  button to show,  however,  requires more than just a setting of  AllowPrinter.   You must assign a  PrinterSettings  object to it.   You won't get an Exception if you don't;  you just won't get the  "Printer"  button to show.   See the following code.

In addition,  there is a slight difference in the code for the two settings—page settings vs.  printer settings.   Note the presence of the word  Default  in the  PageSettings  object:     (Kalani, p. 753)

      PageSetupDialog psd = new PageSetupDialog();
      psd.PageSettings = new PageSettings();
      psd.PrinterSettings = new PrinterSettings();
      if (psd.ShowDialog() == DialogResult.OK)
      {
        printDocument1.DefaultPageSettings = psd.PageSettings;
        printDocument1.PrinterSettings = psd.PrinterSettings;
      }
      //Otherwise the default settings are used.
As in the previous example, you must retain your old copy of  PageSettings  and  PrinterSettings,  or this assignment of  new  objects for these settings will cause the loss of your previous settings.

PrintPreviewDialog

This displays the appearance of the printed result on the monitor,  without actually printing.

When you display this dialog,  the  PrintPage  event occurs  (and repeats as needed)  just as it would if you were actually sending output to a printer.

The  entire  document is set up  (or  "printed")  before the first page is displayed in this dialog—that is,  you do not see the result of each individual call to the  PrintPageEvent  as it occurs.   (If the job is long and you cancel the display during processing,  you will still see the pages that were  "printed"  before the cancellation.)

This dialog displays its own dialog while it is  "printing",  with a "Cancel" button and the number of the page being processed.

Within this dialog,  you can control

  • printing from the dialog
  • adjusting the zoom
  • 1- ,  2-,  3-,  4-,  and 6-page display modes
  • scrolling through the document

To display a  PrintPreviewDialog,  you must set the name of the document being previewed into the  Document  property of the dialog object:    (Kalani, p. 753)

[Picture of the PrintPreviewDialog]

(Kalani has a different example on p. 755)

    PrintPreviewDialog ppd = new PrintPreviewDialog();
    ppd.Document = printDocument1;
    ppd.ShowDialog();

PrintPreviewControl

This is a Control,  not a dialog,  and you add it to your Form from the Toolbox,  like any other Control.

This Control displays the same information as the  PrintPreviewDialog,  but it allows more programmatic settings than the dialog.

There are two reasons to use this Control,  as opposed to the  PrintPreviewDialog:

All you have to do to get this control to display your printed document is set its  Document  property,  which tells the Control what to display.   So if you put a statement like

printPreviewControl1.Document = printDocument1;
in your Form's Constructor,  you will cause calls to the  PrintPage  event as your program is starting up.   (You probably wouldn't want to do this.)

This dialog displays its own dialog while it is  "printing",  with a "Cancel" button and the number of the page being processed.

The document is  not  automatically re-displayed  (in a new format)  if the page or printer settings are changed.   You have to re-assign the document to the Control to do that.

Some properties of the  PrintPreviewControl:


If you're still interested in this subject,  you might want to look at some  downloadable code.
The  (parenthesized references)  in this list are explained  here.  
Here are the relevant questions from the two tests at the end of the "short Kalani"  book:

    Test 1:  question  41  (page 375;  answer:  page 396).

    Test 2:  questions  10  (page 408;  answer:  page 440),  34.

(Some of these questions may duplicate those in long Kalani.   Both texts are listed  here.)


Last  revised  Jan. 5, 2005