Printer Margins, Part 2

Lining up the margin boundary to the edge of the paper instead of the edge of the page boundary


April 03, 2003
URL:http://drdobbs.com/printer-margins-part-2/184416825

Printer Margins, Part 2

In the previous newsletter, our goal was to print within the real page boundary of a printer’s physical capabilities, which lead to developing a method called “GetRealPageBounds” that works in the face of preview printing and alternating scaling. While the page bounds are useful for headers and footers, for the bulk of the printed content, however, you should be printing inside of the MarginBounds rectangle:

void printDocument1_PrintPage(object sender, PrintPageEventArgs e) {
Graphics g = e.Graphics;
g.DrawString(..., e.MarginBounds);
}

Unfortunately, because the MarginBounds is offset from the PageBounds and the PageBounds is offset to stay inside the printable region of the page, the MarginBounds will often be lined up at offsets that don't match the user-specified margins along the edge of the page.

For example, on my Hewlett-Packard LaserJet 2100, the left edge of the PageBounds rectangle is actually 1/4 of an inch in from the left edge and the top edge is 1/8th of an inch down from the top. This affects the MarginBounds, lining up the 1 inch margin I expect at 1-1/4 inches from the left edge of the page. This poses a problem since neither the PageBounds nor the VisibleClipBounds nor any other information provided by WinForms will actually tell you how much the offset of the PageBounds is from the actual edge of the paper.

To get the physical offsets, you need to turn to interop with Win32 and the GetDeviceCaps function. Using that, you can get the printer's physical X and Y offset from the top left and adjust the margins appropriately. However, the X and Y offset will be in printer coordinates, which may not be the same units as the MarginBounds, so you'll have to convert those units as well. The following helper methods do all of that work:

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, DeviceCapsIndex index);

enum DeviceCapsIndex {
  PhysicalOffsetX = 112,
  PhysicalOffsetY = 113,
}

// Adjust MarginBounds rectangle when printing based
// on the physical characteristics of the printer
static
  Rectangle GetRealMarginBounds(PrintPageEventArgs e, bool preview) {
  if( preview ) return e.MarginBounds;

  int cx = 0;
  int cy = 0;
  IntPtr hdc = e.Graphics.GetHdc();

  try {
    // Both of these come back as device units and are not
    // scaled to 1/100th of an inch
    cx = GetDeviceCaps(hdc, DeviceCapsIndex.PhysicalOffsetX);
    cy = GetDeviceCaps(hdc, DeviceCapsIndex.PhysicalOffsetY);
  }
  finally {
    e.Graphics.ReleaseHdc(hdc);
  }

  // Create the real margin bounds by scaling the offset
  // by the printer resolution and then rescaling it
  // back to 1/100th of an inch
  Rectangle marginBounds = e.MarginBounds;
  int dpiX = (int)e.Graphics.DpiX;
  int dpiY = (int)e.Graphics.DpiY;
  marginBounds.Offset(-cx * 100 / dpiX , -cy * 100 / dpiY);
  return marginBounds;
}
 

The GetRealMarginBounds method will take preview mode into account and, when using a real printer, adjust the MarginBounds using the physical offsets, always returning a rectangle in the same units. With this in place, you can safely print inside the margins based on the edges of the paper, as you'd expect:

 void printDocument1_PrintPage(object sender, PrintPageEventArgs e) {
  ...
  g.DrawString(..., GetRealMarginBounds(e));
}
 

As an alternative to using these helper functions, the .NET 1.1 Framework provides a property on the PrintDocument called OriginAtMargins. This property defaults to False, but setting it to True sets the offset of the PageBounds rectangle to be at the margin offset from the physical edge of the page, giving you the ability to print at the appropriate margins using the PageBounds rectangle. However, this property doesn't have any affect in preview mode, doesn't adjust the PageBounds size and keeps the MarginBounds as offset from the now further offset PageBounds, so I don't find it particularly useful when compared with the GetRealPageBounds and GetRealMarginBounds helper methods.

This material is excerpted from the forthcoming Addison-Wesley title Windows Forms Programming in C# by Chris Sells (0321116208). Please note the material presented here is an initial DRAFT of what will appear in the published book.


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.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.