Margins


The PageBounds rectangle property of the PrintPageEventArgs class represents the entire rectangle of the page, all the way to the edge. The MarginBounds rectangle represents the area inside the margins. Figure 7.7 shows the difference.

Figure 7.7. PageBounds Versus MarginBounds

Both PageBounds and MarginBounds are always scaled to units of 100 dpi, so a standard 8.5 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. For example, my laser printer is 600 x 600 dpi.

The margin is useful not only 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 the margins, such as for headers or footers.

However, because 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 can use the Graphics object's 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 VisibleClipBounds contains nonsense values when the page is previewed, so in that case, the PageBounds rectangle should be used. Also, if the Graphics object is using a nondefault PageUnit (as discussed in Chapter 6: Advanced Drawing), 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:

  // Get real page bounds based on printable area of the page   static   Rectangle GetRealPageBounds(PrintPageEventArgs e, bool preview) {  // 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));  }  

GetRealPageBounds returns the PageBounds rectangle if in preview mode [1] and always scales the returned Rectangle in the same units. This helper allows you to write your printing code to stay within the real bounds of the page:

[1] Whether the application is printing in preview mode must be managed by the application itself. There is nothing to indicate printing versus print preview in the printing classes.

  // 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 StringFormat farFormat = new StringFormat(); farFormat.Alignment = StringAlignment.Far; farFormat.LineAlignment = StringAlignment.Far; g.DrawString("footer", font, Brushes.Black, pageBounds, farFormat); 

For the bulk of the printed content, however, you should be printing inside the MarginBounds rectangle:

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

Unfortunately, because MarginBounds is offset from PageBounds and because PageBounds is offset to stay inside the printable region of the page, MarginBounds is often 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 ¼ inch in from the left edge of the page, and the top edge is 1/8 inch down from the top. This affects MarginBounds, lining up the 1-inch margin I expect at 1 ¼ inches from the left edge of the page. This poses a problem because neither PageBounds nor VisibleClipBounds nor any other information provided by WinForms actually tells you how much the PageBounds is offset from the edge of the paper.

To get the physical offsets, you must turn to interoperability 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 is in printer coordinates, which may not be the same units as MarginBounds, so you must convert those units as well. The following helper methods do all 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 takes preview mode into account and, when you use a real printer, adjusts 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, this.preview)  ); } 

As an alternative to using these helper functions, the .NET 1.1 Framework provides a property on 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, letting you print at the appropriate margins using the PageBounds rectangle. However, this property doesn't have any effect in preview mode, doesn't adjust the PageBounds size, and keeps the MarginBounds as offset from the now further offset PageBounds. For these reasons, I don't find it particularly useful when compared with the GetRealPageBounds and GetRealMarginBounds helper methods.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net