Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Printer Margins, Part 1


Serialization Basics, Part 3

The basic printing architecture in WinForms makes it pretty easy to draw output to the printer. The central player in the print architecture is the PrintDocument, available on the VS.NET Toolbox. Dropping a print document onto a form, handling the document’s PrintPage event, and calling the document’s Print method does the deed:

<b>PrintDocument printDocument1;</b>

void InitializeComponent() {
 <b> this.printDocument1 = new PrintDocument();</b>
  ...
  <b>this.printDocument1.PrintPage +=
    new PrintPageEventHandler(this.printDocument1_PrintPage);</b>
  ...
}

void printButton_Click(object sender, System.EventArgs e) {
  <b>printDocument1.Print();</b>
}

<b>void printDocument1_PrintPage(object sender, PrintPageEventArgs e) {</b>
  // Draw onto the printer
  Graphics g = e.Graphics;
  using( Font font = new Font("Lucida Console", 72) ) {
    g.DrawString("Hello,\nPrinter", font, ...);
  }
<b>}</b>

There are all kinds of other things you can do, like showing the print dialog or the page settings dialog. One of the most important things you'll need to do is make sure that you stay within the physical boundaries of the printer as well as the bounds of the margin that the user has specified for their printing.

Printing Bounds

The PageBounds rectangle property of the PrintPageEventArgs class (provided to the PrintPage event) represents the entire rectangle of the page, all the way to the edge. The MarginBounds rectangle represents the inside of the margins. Figure 1 shows the difference (using the PrintPreviewControl hosted on a form).

Figure 1: PageBounds vs. MarginBounds

Both PageBounds and MarginBounds are always scaled to units of 100 dpi, so a standard 8 x 11 inch piece of paper will always have a PageBounds rectangle {0, 0, 850, 1100}. With the default margin of 1 inch all the way around, the MarginBounds will be at {100, 100, 650, 900}. To match the bounds, by default the GraphicsUnit for the Graphics object will be 100 dpi, too, and will be scaled to whatever is appropriate for the printer resolution, like my laser printer, which is 600 x 600 dpi.

The margin is useful not just because users often want some white space around their pages when they print, but also because many printers can't print to the edge of the page, so anything printed all the way to the edge is bound to be cut off to some degree. To avoid this, the Graphics object you get when you're printing starts at the top-left corner of the printable area of the page. That's useful for printing outside of the margins, like for headers or footers.

However, since printers normally can't print to the edge of the page, the PageBounds rectangle will be too large. To get the actual size of the bounding rectangle, you could use the Graphics object VisibleClipBounds rectangle:

// Get a page bounds with an accurate size
RectangleF pageBounds = e.Graphics.VisibleClipBounds;

// Draw a header
g.DrawString("header", font, Brushes.Black, pageBounds);

Unfortunately, for some reason the VisibleClipBounds contains nonsense values when previewing, so in that case, the PageBounds rectangle should be used. Also, if the Graphics object is using a nondefault PageUnit, VisibleClipBounds will be in different units than PageBounds (which is always in units of 100 dpi). To handle these variables, it's useful to have a helper method to return the "real page bounds in a consistent unit of measure:

<b>
// Get real page bounds based on printable area of the page
static
  Rectangle GetRealPageBounds(PrintPageEventArgs e, bool preview) {</b>
  // Return in units of 1/100th of an inch
  if( preview ) return e.PageBounds;

  // Translate to units of 1/100th of an inch
  RectangleF vpb = e.Graphics.VisibleClipBounds;
  PointF[]
    bottomRight = { new PointF(vpb.Size.Width, vpb.Size.Height) };
  e.Graphics.TransformPoints(
    CoordinateSpace.Device, CoordinateSpace.Page, bottomRight);
  float dpiX = e.Graphics.DpiX;
  float dpiY = e.Graphics.DpiY;
  return new Rectangle(
    0,
    0,
    (int)(bottomRight[0].X * 100 /  dpiX),
    (int)(bottomRight[0].Y * 100 / dpiY));
<b>}</b>

The GetRealPageBounds will return the PageBounds rectangle if in preview mode and will scale the returned Rectangle to always be in the same units. This helper allows you to write your printing code to stay within the real bounds of the page:

<b>
// Get the real page bounds
Rectangle pageBounds = GetRealPageBounds(e, this.preview);

// Draw a header in the upper left
g.DrawString("header", font, Brushes.Black, pageBounds);

// Draw a footer in the lower right</b>
StringFormat farFormat = new StringFormat();
farFormat.Alignment = StringAlignment.Far;
farFormat.LineAlignment = StringAlignment.Far;
<b>g.DrawString("footer", font, Brushes.Black, pageBounds, farFormat);</b>

The page boundary isn't the only thing that needs a little work to get just right. Getting the margin boundary to line up with the edge of the paper instead of the edge of the page boundary requires some fancy footwork, too, and that's the subject of next month's installment.

This material is an excerpted draft from the forthcoming Addison-Wesley title Windows Forms Programming in C#, by Chris Sells (0321116208).


Chris Sells in an independent consultant specializing in distributed applications in .NET and COM, as well as an instructor for DevelopMentor. He's written several books including ATL Internals, which is currently being updated for ATL7. He's also working on Essential Windows Forms for Addison-Wesley and Mastering Visual Studio .NET for O'Reilly. In his free time, Chris directs the Genghis source-available project. If you enjoy this newsletter, you may be interested in theSellsBrothers News, which notifies subscribers of the various projects that Chris is working on. Subscribe or browse the archives at http://www.sellsbrothers.com/newsletter.



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.