Click here to Skip to main content
Licence 
First Posted 1 Jul 2004
Views 95,783
Bookmarked 44 times

Getting the printer's REAL margin bounds in .NET

By | 1 Jul 2004 | Article
The .NET printing classes don't take into account the printer's physical margins. This class fixes this problem, so your printed pages will be using the correct margins.

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 set the page margins to 1 inches 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(); // Get the device context handle

HardMarginLeft = GetDeviceCaps(hDC , PHYSICALOFFSETX);
HardMarginTop  = GetDeviceCaps(hDC , PHYSICALOFFSETY);

e.Graphics.ReleaseHdc(hDC); // Don't forget to release it again

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;  // Get the REAL Margin 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

About the Author

Philippe Leybaert

Web Developer

Belgium Belgium

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralThanks Pinmemberzapper00116:23 14 Jul '10  
GeneralMy vote of 1 PinmemberMember 43719155:35 6 Jan '10  
QuestionHow to get dpi of the unprintable area of a printer? Pinmemberegkua17:06 8 Dec '09  
GeneralThank you PinmemberNagarajanp22:38 15 Nov '06  
GeneralWe need NOT to call API to obtain hard margin in .net Pinmembersuper gun tank0:19 24 Sep '06  
GeneralRe: We need NOT to call API to obtain hard margin in .net PinmemberPhilippe Leybaert9:03 24 Sep '06  
GeneralRe: We need NOT to call API to obtain hard margin in .net Pinmembertermi2:11 25 Oct '06  
GeneralRe: We need NOT to call API to obtain hard margin in .net PinmemberPhilippe Leybaert4:54 25 Oct '06  
GeneralRe: We need NOT to call API to obtain hard margin in .net PinmemberArtiBen3:59 16 May '07  
QuestionCentering text on a page Pinmembersalblomo10:19 24 Aug '06  
AnswerRe: Centering text on a page PinmemberPhilippe Leybaert10:45 24 Aug '06  
GeneralUnable to set Left Margin PinsussN. Hyk11:36 18 Apr '05  
GeneralRe: Unable to set Left Margin PinmemberN. Hoyek3:43 20 Apr '05  
QuestionDeterming the PrinterBounds without printing? Pinmemberashtekar5:31 16 Feb '05  
AnswerRe: Determing the PrinterBounds without printing? PinmemberRenaud Bompuis0:56 5 Jun '05  
QuestionWhat about PrintPreviewDialog Pinmemberbhelzer10:56 13 Jan '05  
AnswerHere's how to know if you are in preview or print mode PinmemberAbsofukinlutely17:16 6 Jul '05  
bhelzer wrote:
Is there something I can do?
 
Maybe this post is a bit late, but this works:
''' <summary>Get the MarginBounds to use in PrintDocument.PrintPage</summary>
''' <param name="doc">The printDocument to use for the PrintController</param>
''' <param name="e">The PrintPageEventArgs to use for the margins</param>
''' <param name="previewmode">[ByRef] Is set to true if previeweing the document</param>
''' <returns>The bounds to use for drawing on the page instead of e.MarginBounds</returns>
Public Shared Function GetBoundsForPrinting(ByVal doc As Drawing.Printing.PrintDocument, _
    ByVal e As Drawing.Printing.PrintPageEventArgs, ByRef previewmode As Boolean) As Rectangle
    'The bounds to use for your painting,
    Dim printbounds As Rectangle
 
    If (TypeOf doc.PrintController Is PrintControllerWithStatusDialog) Then
        'PrintControllerWithStatusDialog has a field underlyingController, 
        'which is the REAL controller (it does all the hard work, 
        'PrintControllerWithStatusDialog is just a wrapper)

        'Type of the PrintController
        Dim type As Type = doc.PrintController.GetType
        'The private variable underlyingController of PrintControllerWithStatusDialog
        Dim field As Reflection.FieldInfo = type.GetField("underlyingController", 
            Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
        'Get the value of underlyingController
        Dim controller As Object = field.GetValue(doc.PrintController)
        'Check to see if underlyingController is a PreviewPrintController,
        'that means we are in preview mode
        If (TypeOf controller Is Drawing.Printing.PreviewPrintController) Then
            'We are in preview mode, so set printbounds to e.Marginbounds
            printbounds = e.MarginBounds
            previewmode = True
        Else
            'We are really printing, so set printbounds to the REAL printerbounds
            printbounds = (New Printing.PrinterBounds(e)).Bounds
            previewmode = False
        End If
 
    Else
        'Now we have a problem: we still don't know what to do
        'Probably you have used a custom PrintController or something
        'Sorry i cant help you any further...

        'Just say that we are really printing, so that at least
        'the paper that rolls out of the printer looks fine
        printbounds = (New Printing.PrinterBounds(e)).Bounds
        previewmode = False
    End If
 
    Return printbounds
End Function 
I used to struggle with this also for a very long time, until i used this.
I just call this function at the start of every PrintDocument.PrintPage.
 
Hope this can help some people out! Cool | :cool:
 
BTW, maybe Phil can put this function is his class or something...
AnswerRe: What about PrintPreviewDialog PinmemberPrestaul10:42 27 Mar '06  
QuestionIs there ANOTHER printing bug in the .NET framework? Pinmemberreflex@codeproject4:48 3 Jul '04  
AnswerRe: Is there ANOTHER printing bug in the .NET framework? Pinmemberwebstorm7:38 10 Apr '07  
General2.0 Beta 1 framework Pinmembernxtwothou5:34 2 Jul '04  
GeneralRe: 2.0 Beta 1 framework PinmemberPhilippe Leybaert3:02 3 Jul '04  
GeneralRe: 2.0 Beta 1 framework PinmemberPhilippe Leybaert3:51 3 Jul '04  
GeneralRe: 2.0 Beta 1 framework Pinmemberdarwind17:47 5 Feb '07  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120528.1 | Last Updated 2 Jul 2004
Article Copyright 2004 by Philippe Leybaert
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid