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:
- Add the namespace directive
using System.Drawing.Printing;
- Use the IDE to add a PrintDocument
component to the Form
(the component has no visual aspect, so it winds up in the component
tray).
- Add an event handler for this
PrintDocument object's
PrintPage event,
and add code to this event to do the actual printing.
This event passes you two arguments, the second of which is
PrintPageEventArgs.
This in turn has two crucial properties, which you must use in
your printing:
- Use the methods of the Graphics
object to draw your graphics objects
- Use the HasMorePages
property to say whether there are more pages to print
- When you want to print at run time,
- Invoke printDocument1.Print();
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 wayand 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:
- Cancel. Set to
true if the print job should be
cancelled. However, both the
PrintPreviewDialog and the
PrintPreviewControl
display a small dialog box of their own while "printing",
which states the page number being processed
and gives you a "Cancel" button.
You do not have to program your own "Cancel"
button if you use either of these objects.
If you want to cancel a print job from code (for instance,
if a page limit has been exceeded), you may set this
Cancel switch from either
the PrintPage event or the
QueryPageSettings event.
- Graphics. The device-independent
Graphics object used for printing.
- HasMorePages. You set this to
true if there are more pages to print.
It forces another occurrence of the
PrintPage event.
- MarginBounds.
Page's printable area.
A Rectangle structure.
- PageBounds.
The entire page.
A Rectangle structure.
- PageSettings.
A PageSettings
object with further switches controlling printing,
such as page orientation.
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)
- Bounds
- Color
- Landscape
- Margins
- PaperSize
- PaperSource
- PrinterResolution
- PrinterSettings.
Yes, you can use this to get to the next set of properties.
PrinterSettings properties
(well, some of them, anyway):
- CanDuplex.
Boolean. Printer supports two-sided printing
- Collate.
Boolean. Output is being collated
- Copies
- DefaultPageSettings
- Duplex.
Boolean. Printer is printing on two sides
- InstalledPrinters.
The names of the printers on the system
- PaperSizes.
The ones supported by the printer
- PrinterName
- PrinterResolutions
- PrintRange. Page numbers
- PrintToFile. Boolean
- SupportsColor.
Boolean.
Where to reset PageSettings andc
PrinterSettings
There are two places you are expected to change these settings:
- System dialog boxes, which you can invoke from your code:
- PrintDialog, and
- PageSetupDialog.
Both of these dialog boxes are described below,
under "Dialogs and Components controlling printing".
- 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:
- First, you should realize that settings made in
QueryPageSettings persist to the next invocation
of QueryPageSettingsthat is,
the next time this event occurs, you do not
get the original default settings.
- Second, although you are not told you may do so,
you can to set at least some of the properties in
PageSettings in the
PrintPage event.
But such settings will not
be effective for the page being printed by that invocation of the
PrintPage event;
they will only be effective for its next invocation.
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
areathat 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 pageunless 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")
- SmoothingMode.
"Specifies whether smoothing (antialiasing) is applied to lines
and curves and the edges of filled areas.
"The SmoothingMode property does not affect text.
To set the text rendering quality,
use the TextRenderingHint enumeration.
"The smoothing mode does not affect areas filled by a
path gradient brush.
Areas filled using a PathGradientBrush
object are rendered the same way (aliased)
regardless of the SmoothingMode
property."
An important thing to remember is that this property refers to the printing
of graphics, not text.
Here is the SmoothingModeEnumeration:
(Help: "SmoothingMode enumeration")
| AntiAlias |
Specifies antialiased rendering. |
| Default |
Specifies the default mode. |
| HighQuality |
Specifies high quality, low speed rendering. |
| HighSpeed |
Specifies high speed, low quality rendering. |
| Invalid |
Specifies an invalid mode.
That's all on-line Help says:
"Specifies an invalid mode."
Attempting to set the
SmoothingMode
to Invalid
results in a big fancy Exception. |
| None |
Specifies no antialiasing. |
Finally, we should mention the namespace for this enumeration:
it's System.Drawing.Drawing2D, for heaven's sake.
Well, maybe we should say something about a property that
Kalani doesn't mention:
- TextRenderingHint.
"Specifies the quality of text rendering . . . .
The quality ranges from
text (fastest performance and lowest quality)
to antialiased text (better quality but slower performance)
to ClearType text (best quality on an LCD display)."
Don't be misled by that mention of an LCD display:
this enumeration applies to printing as well as to a monitor display.
Note that this property refers to the printing of text,
not of graphics.
And what's its namespace?
System.Drawing.Text, of course.
I spare you its enumeration.
(Help: "TextRenderingHint 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: 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: 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 settingspage 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 dialogthat 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)
|
(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:
- You can place this Control on your Windows Form and fill it without bringing
up a separate dialog box.
- You have closer control over the behavior of the Control,
and of the way it initially displays.
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:
- AutoZoom.
"true if the changing the control size or number of pages adjusts the Zoom property;
otherwise, false. The default is true."
(Help: "PrintPreviewControl.AutoZoom Property ")
- Columns.
The number of columns in the display.
- Document. What to print.
Required; see above.
(Kalani, example, p. 757,
step 6)
- Rows. The number of rows in the display.
- StartPage.
"The page number of the upper left page. The default is 0."
(Help: "PrintPreviewControl.StartPage Property")
- UseAntiAlias
- Zoom.
"How large the pages will appear. The value 1.0 is full size."
(Help: "PrintPreviewControl.Zoom Property")
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