Windows Forms 2.0 Programming (Microsoft .NET Development Series)
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 8.8 shows the difference. Figure 8.8. 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 is at {100, 100, 750, 1000}. To match the bounds, by default the GraphicsUnit for the Graphics object is 100 dpi, too, and is 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 printed pages, 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 visibleClipBounds = e.Graphics.VisibleClipBounds; // Draw a header g.DrawString("header", printerfont, Brushes.Black, visibleClipBounds);
If the Graphics object is using a nondefault PageUnit,[2] then 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: [2] PageUnit is discussed in Chapter 7 // Get real page bounds based on printable area of the page static Rectangle GetRealPageBounds(PrintPageEventArgs e, bool preview) { // Return in units of 1/100 inch if( preview ) return e.PageBounds; // Translate to units of 1/100 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 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: void printDocument_PrintPage(object sender, PrintPageEventArgs e) { // Draw to the e.Graphics object that wraps the print target Graphics g = e.Graphics; // Get the real page bounds Rectangle realPageBounds = GetRealPageBounds(e, preview); // Draw a header in the upper left g.DrawString("header", printerfont, Brushes.Black, realPageBounds); // Draw a footer in the lower right StringFormat farFormat = new StringFormat(); farFormat.Alignment = StringAlignment.Far; farFormat.LineAlignment = StringAlignment.Far; g.DrawString( "footer", printerfont, Brushes.Black, realPageBounds, farFormat); }
For the bulk of the printed content, however, you should print inside the MarginBounds rectangle: void printDocument_PrintPage(object sender, PrintPageEventArgs e) { // Draw to the e.Graphics object that wraps the print target Graphics g = e.Graphics; ... g.DrawString("Content", printerfont, Brushes.Black, 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, and the top edge is 1/8 inch down from the top. This affects the 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 the PageBounds nor the VisibleClipBounds actually tells you how much the offset of the PageBounds is from the actual edge of the paper. The PageSettings class does tell youvia its PrintableArea property, which returns a RectangleF (the PageBounds plus the printer offsets). However, it turns out to be just a little bit easier to determine a printer's physical X and Y offsets from the top left by using PageSettings.HardMarginX and PageSettings.HardMarginY. You can then use these values to adjust the margins appropriately. However, the X and Y offsets are in printer coordinates, which may not be the same units as the MarginBounds, so you must convert those units as well. The following helper methods do all that work: // 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; // Get printer's offsets float cx = e.PageSettings.HardMarginX; float cy = e.PageSettings.HardMarginY; // Create the real margin bounds by scaling the offset // by the printer resolution and then rescaling it // back to 1/100 inch Rectangle marginBounds = e.MarginBounds; float dpiX = e.Graphics.DpiX; float dpiY = e.Graphics.DpiY; marginBounds.Offset((int)(-cx * 100 / dpiX), (int)(-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 printDocument_PrintPage(object sender, PrintPageEventArgs e) { // Draw to the e.Graphics object that wraps the print target Graphics g = e.Graphics; ... RectangleF realMarginBounds = GetRealMarginBounds(e, preview); g.DrawString( "Content", printerfont, Brushes.Black, realMarginBounds); }
As an alternative to using these helper functions, the .NET 2.0 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. |
Категории