Windows Forms 2.0 Programming (Microsoft .NET Development Series)
In Chapters 5 and 6, we've concentrated on drawing to the screen. By default, if you're drawing in the Paint event handler, you're drawing in units of pixels. Even if you create a Graphics object from a form using Form.CreateGraphics, you draw in units of pixels. This is handy because the units of the various user interface elements (such as the client rectangle) and the position and sizes of the controls are all in pixels. Pixels translate into real-world coordinates based on system settings for Normal or Small (versus Large or Custom) fonts, the resolution at which the display adapter runs, and the size of the monitor. Taking all that into account, only some of which is available programmatically, it would be remarkably difficult to display physically correct sizes on a monitorfor example, the ruler you see at the top of a word processing program. Luckily, because you can usually adjust all this using various systemwide and application-specific settings, people generally size things so that they are comfortable, and the real-world sizes are not so important. That is, they're not important until you need to output to a specific physical size (such as to a printer). For example, it's not important that the ruler at the top of the document I'm typing this sentence into is currently showing an inch as 19/16 inches.[1] What is important is the proportion of the dimensions of each line to the units shown as "inches" as compared to the width of each line as I type. The principle of WYSIWYG (what you see is what you get) dictates that I should be able to print something very similar to what I'm seeing on the screen. When my word processing program shows a line wrapping at a certain word when I get close to the 6.5-inch area inside my margins (standard 8.5-inch wide paper with a 1-inch margin on each side), I want that same wrap to happen at the same word when I print the document. To make that happen, we need to write programs that can wrap text at units other than pixels, like the one shown in Figure 7.1. [1] I measured it with a ruler from the physical world. Figure 7.1. Manually Drawing in Inches
Figure 7.1 shows a ruler marked off in half-inch increments and text wrapped to a right margin of 6.5 inches. The numbers in the status strip represent the dimensions of the form's client area in both pixels and the real-world inch values they equate to. We accomplish this by using the following function to manually convert coordinates and sizes to inches:[2] [2] See Chapter 6 for further discussion of Graphics.DpiX and Graphics.DpiY. float InchesToPixels(float inches) { using( Graphics g = this.CreateGraphics() ) { return inches * g.DpiX; } }
This function is used to calculate the width of the ruler, the half-inch tick marks, and the width of the text box. For example, the code that draws the outline of the ruler looks like this: using( Font rulerFont = new Font("MS Sans Serif", 8.25f) ) { int pixelsPerInch = 72; // Inches float rulerFontHeight = rulerFont.SizeInPoints / pixelsPerInch; // Specify units in inches RectangleF rulerRect = new RectangleF(0, 0, 6.5f, rulerFontHeight * 1.5f); // Draw in pixels g.DrawRectangle( Pens.Black, InchesToPixels(rulerRect.X), InchesToPixels(rulerRect.Y), InchesToPixels(rulerRect.Width), InchesToPixels(rulerRect.Height)); ... } The conversion from inches to pixels is necessary because the units of the Graphics object passed to the Paint event are pixels, which represent the device units for the display adapter. All units eventually need to be translated to device units for rendering, but this doesn't mean that you need to specify drawing in device units. Instead, the Graphics object draws with page units, which default to pixels in the Paint event but don't need to stay that way. The PageUnit and PageScale properties of the Graphics object allow you to specify different units in which to draw: // Set page units and scale g.PageUnit = GraphicsUnit.Inch; g.PageScale = 1; // 1 unit is 1 inch using( Font rulerFont = new Font("MS Sans Serif", 8.25f) ) using( Pen blackPen = new Pen(Color.Black, 0) ) { float rulerFontHeight = rulerFont.GetHeight(g); // Inches // Specify units in inches RectangleF rulerRect = new RectangleF(0, 0, 6.5f, rulerFontHeight * 1.5f); // Draw in inches g.DrawRectangle( blackPen, rulerRect.X, rulerRect.Y, rulerRect.Width, rulerRect.Height); ... }
Before the code does any drawing, the first thing it does is to set the page unit for the graphics object to GraphicsUnit.Inch and the page scale to 1, which turns every 1 unit (whether it's specified for a position or a size) into 1 inch.[3] Notice that we're using floating-point numbers to enable fractional inches; the floating-point numbers are converted to device units by the Graphics object. The PageUnit property can be any value from the GraphicsUnit enumeration, so units can be in points or millimeters as well as pixels or inches. The PageScale can be a floating-point number, so if we wanted to specify a scale of 0.1 when specifying a PageUnit of Inch, then 1 unit would equal 0.1 inch, and 10 units would equal 1 inch. [3] The GraphicsUnit enumeration is covered in Chapter 5. Note the use of a new black pen, in spite of the presence of the Pens.Black pen that was used in the earlier example. All the default pens default to 1 unit in width. When the unit was pixels, that was fine, but when the unit is inches, a 1-unit pen became 1-inch wide. Pens are specified in units that are interpreted when the pen is used. To avoid having a very wide pen, the code specifies 0 for the width of the pen, and that causes the pen to be 1 device unit wide no matter what the page unit is currently set to. Also note that the Font object is not affected by the page units. Instead, recall from Chapter 6 that we specify Fonts using a GraphicsUnit argument passed to the constructor, and they default to GraphicsUnit.Point. Finally, notice that the code uses the GetHeight method of the Font class, passing the Graphics object. Unlike the Height property, the GetHeight method is scaled appropriately to the current units of the Graphics object. Converting Pixels to Page Units
If a method doesn't take a Graphics object as an argument, then it isn't affected by the page units. For example, the ClientRectangle of the form or control being drawn is always specified in pixels, making some consideration necessary when you use units other than pixels. To convert back and forth between device and page units, the Graphics object provides the TransformPoints method: using( Graphics g = this.CreateGraphics() ) { // Set page unit to inches g.PageUnit = GraphicsUnit.Inch; g.PageScale = 1; PointF[] bottomRight = new PointF[] { new PointF(this.ClientSize.Width, this.ClientSize.Height) }; // Convert client size from pixels to inches g.TransformPoints( CoordinateSpace.Page, // Destination CoordinateSpace.Device, // Source bottomRight); ... }
This code converts to page units (set to inches in this example) from device units (also known as pixels) using the TransformPoints method, which can convert between any type of coordinates from the CoordinateSpace enumeration. CoordinateSpace has the following values: namespace System.Drawing.Drawing2D { enum CoordinateSpace { Device = 2, Page = 1, World = 0, } }
The value we haven't yet discussed is CoordinateSpace.World, which is a whole other world of coordinates (if you'll excuse the pun). |
Категории