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.5x11 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, our laser printer is 600x600 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 Dim pageBounds As RectangleF = 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 Shared Function GetRealPageBounds(e As PrintPageEventArgs, _       preview As Boolean) As Rectangle   ' Return in units of 1/100th of an inch   If preview Then return e.PageBounds   ' Translate to units 1/100th of an inch   Dim vpb As RectangleF = e.Graphics.VisibleClipBounds   Dim bottomRight() As PointF = { _       New PointF(vpb.Size.Width, vpb.Size.Height)}   e.Graphics.TranformPoints( _       CoordinateSpace.Device, CoordinateSpace.Page, bottomRight)   Dim dpiX As Single = e.Graphics.DpiX   Dim dpiY As Single = e.Graphics.DpiY   Return New Rectangle( _       0, _       0, _       CInt(bottomRight(0).X * 100 / dpiX), _       CInt(bottomRight(0).Y * 100 / dpiY)) End Function 

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 Dim pageBounds As Rectangle = GetRealPageBounds(e, Me.preview) ' Draw a header in the upper left g.DrawString("header", font, Brushes.Black, pageBounds) ' Draw a footer in the lower right Dim farFormat As StringFormat = 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:

 
 Sub printDocument1_PrintPage(sender As Object, e As PrintPageEventArgs)   Dim g As Graphics = e.Graphics   g.DrawString(..., e.MarginBounds) End Sub 

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 our 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 inch down from the top. This affects MarginBounds, lining up the 1-inch margin we 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", _ EntryPoint := "GetDeviceCaps")> _ Shared Function GetDeviceCaps(hdc As IntPtr, index As DeviceCapsIndex) _    As Integer End Function Enum DeviceCapsIndex   PhysicalOffsetX = 112   PhysicalOffsetY = 113 End Enum ' Adjust MarginBounds rectangle when printing based ' on the physical characteristics of the printer Shared Function GetRealMarginBounds(e As PrintPageEventArgs, _   preview As Boolean)   If preview Then Return e.MarginBounds   Dim cx As Integer = 0   Dim cy As Integer = 0   Dim hdc As IntPtr = 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)   End Try   ' Create the real margin bounds by scaling the offset   ' by the printer resolution and then rescaling it   ' back to 1/100th of an inch   Dim marginBounds As RectangleF = e.MarginBounds   Dim dpiX As Integer = CInt(e.Graphics.DpiX)   Dim dpiY As Integer = CInt(e.Graphics.DpiY)   marginBounds.Offset(-cx * 100 / dpiX, -cy * 100 / dpiY)   Return marginBounds End Sub 

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:

 
 Sub printDocument1_PrintPage(sender As Object, e As PrintPageEventArgs)   ...   g.DrawString(..., GetRealMarginBounds(e)) End Sub 

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, we don't find it particularly useful when compared with the GetRealPageBounds and GetRealMarginBounds helper methods.



Windows Forms Programming in Visual Basic .NET
Windows Forms Programming in Visual Basic .NET
ISBN: 0321125193
EAN: 2147483647
Year: 2003
Pages: 139

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