Introduction
In this article, I present a very useful class that works around a serious limitation in the .NET printing classes. The class PrinterBounds
retrieves the real printing bounds of a printed page. The .NET printing classes don't take into account the physical left and top margins of the printer.
Background
When you want to print a document using .NET, you create a PrintDocument
object, and subscribe to the PrintPage
event handler. Let's say you want to set the page margins to 1 inch on each side, and draw a 2 inch rectangle in the top-left corner of the page (using these margins):
private void printDoc_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
Rectangle r = e.MarginBounds;
e.Graphics.DrawRectangle(Pens.Black , r.Left , r.Top , 200 , 200);
e.HasMorePages = false;
}
Looks easy, right? But there's a serious problem when you look at the printed output (it looks fine in a print preview). You will notice that the rectangle is not at 1 inch from the left and top edge, but maybe 1.2 inches.
The reason is simple: The MarginBounds
property doesn't know anything about the physical left and right margins for your printer (the non-printable region), and .NET provides no way to find out what these margins are. On the other hand, the width and height of the MarginBounds
rectangle DOES take into account the hard margins of your printer, but the offset (Left
and Top
) is incorrect. (I have no idea why Microsoft didn't fix this in v1.1 of the Framework.)
The PrinterBounds
class presented here solves this problem.
Implementation
To determine the printer's physical margins, we need to call a Win32 function from gdi32.dll: GetDeviceCaps()
[DllImport("gdi32.dll")] private static extern Int32
GetDeviceCaps(IntPtr hdc, Int32 capindex);
We can then use it like this:
HardMarginLeft = GetDeviceCaps(hDC , PHYSICALOFFSETX);
HardMarginTop = GetDeviceCaps(hDC , PHYSICALOFFSETY);
Of course, we need the handle to the device context of the printer, which is not too hard, since we have a reference to the Graphics
object in the PrintPageEventArgs
parameter of the PrintPage
event handler:
IntPtr hDC = e.Graphics.GetHdc();
HardMarginLeft = GetDeviceCaps(hDC , PHYSICALOFFSETX);
HardMarginTop = GetDeviceCaps(hDC , PHYSICALOFFSETY);
e.Graphics.ReleaseHdc(hDC);
Now we have the margins in device units. To convert these to printer units (1/100 inch), we have to get the DPI of the printer, which is available in the DpiX
and DpiY
properties of our Graphics
object.
Remember I mentioned that the MarginBounds
does have the correct width and height, so we just need to shift the rectangle to the correct position, using the hard margins we just retrieved. The final class looks like this:
public class PrinterBounds
{
[DllImport("gdi32.dll")] private static extern Int32
GetDeviceCaps(IntPtr hdc, Int32 capindex);
private const int PHYSICALOFFSETX = 112;
private const int PHYSICALOFFSETY = 113;
public readonly Rectangle Bounds;
public readonly int HardMarginLeft;
public readonly int HardMarginTop;
public PrinterBounds(PrintPageEventArgs e)
{
IntPtr hDC = e.Graphics.GetHdc();
HardMarginLeft = GetDeviceCaps(hDC , PHYSICALOFFSETX);
HardMarginTop = GetDeviceCaps(hDC , PHYSICALOFFSETY);
e.Graphics.ReleaseHdc(hDC);
HardMarginLeft = (int)(HardMarginLeft * 100.0 / e.Graphics.DpiX);
HardMarginTop = (int)(HardMarginTop * 100.0 / e.Graphics.DpiY);
Bounds = e.MarginBounds;
Bounds.Offset(-HardMarginLeft , -HardMarginTop);
}
}
Using the Code
Using the class is very simple. In your PrintPage
event handler, use an instance of PrinterBounds
instead of the MarginBounds
property of PrintPageEventArgs
:
private void printDoc_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
PrinterBounds objBounds = new PrinterBounds(e);
Rectangle r = objBounds.Bounds;
e.Graphics.DrawRectangle(Pens.Black , r.Left , r.Top , 200 , 200);
e.HasMorePages = false;
}
Points of Interest
Microsoft knew about this problem long before v1.1 of the .NET Framework was released, but they didn't fix this bug. I suppose they didn't want to break existing code. I haven't checked the v2.0 beta of the Framework, but I suspect they left it like this.
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.