Windows Forms 2.0 Programming (Microsoft .NET Development Series)
Of course, deciding on a font is only half the fun. The real action is drawing strings after a font has been picked. For that, you use the DrawString method of the Graphics object: using( Font font = new Font("Arial", 12) ) { // This wraps at new line characters g.DrawString("line 1\nline 2", font, Brushes.Black, 10, 10); }
The DrawString method takes, at a minimum, a string, a font, a brush to fill in the font characters, and a point. DrawString starts the drawing at the point and keeps going until it hits the edges of the region in the Graphics object. This includes translating new line characters as appropriate but does not include wrapping at word boundaries. To get the wrapping, you specify a layout rectangle: using( Font font = new Font("Arial", 12) ) { // This automatically wraps long lines and // it wraps at new line characters g.DrawString("A long string ...", font, Brushes.Black, this.ClientRectangle); } Formatting
If you'd like to turn off wrapping or set other formatting options, you use an instance of the StringFormat class: namespace System.Drawing { sealed class StringFormat : IDisposable, ... { // Constructors public StringFormat(...); // various overloads // Properties public StringAlignment Alignment { get; set; } public int DigitSubstitutionLanguage { get; } public StringDigitSubstitute DigitSubstitutionMethod { get; } public StringFormatFlags FormatFlags { get; set; } public static StringFormat GenericDefault { get; } public static StringFormat GenericTypographic { get; } public HotkeyPrefix HotkeyPrefix { get; set; } public StringAlignment LineAlignment { get; set; } public StringTrimming Trimming { get; set; } // Methods public float[] GetTabStops(out float firstTabOffset); public void SetDigitSubstitution( int language, StringDigitSubstitute substitute); public void SetMeasurableCharacterRanges(CharacterRange[] ranges); public void SetTabStops(float firstTabOffset, float[] tabStops); } }
A StringFormat object lets you set all kinds of interesting text characteristics, such as the tab stops and the alignment (vertically and horizontally) as well as whether to wrap. Because StringFormat implements IDisposable, you create it inside a using statement: // Turn off autowrapping using( StringFormat format = new StringFormat(StringFormatFlags.NoWrap) ) { g.DrawString("...", font, brush, rect, format); }
The StringFormatFlags enumeration provides a number of additional formatting options: [View full width] namespace System.Drawing { enum StringFormatFlags { 0, // No flags (default) DirectionRightToLeft = 1, // Draw text right-to-left DirectionVertical = 2, // Draw text top-to-bottom FitBlackBox = 4, // Characters can't overhang the layout rectangle[5] DisplayFormatControl = 32, // Show format control character glyphs[6] NoFontFallback = 1024, // Don't fall back for characters missing // from font MeasureTrailingSpaces = 2048, // MeasureString includes trailing spaces NoWrap = 4096, // Don't interpret \n or \t (implied when no rect) LineLimit = 8192, // Show only whole lines NoClip = 16384, // Don't clip text partially outside // layout rectangle } }[5] You would think [6] In this context, a You can combine and set one or more StringFormatFlags on a StringFormat object by using either the StringFormat constructor or the FormatFlags property. For example, the following draws text top-to-bottom and disables automatic wrapping: using( StringFormat format = new StringFormat() ) { format.FormatFlags = StringFormatFlags.DirectionVertical | StringFormatFlags.NoWrap; g.DrawString("...", font, brush, rect, format); } If the string is too tall to fit into the allotted space, you have three choices. You can clip to the layout rectangle, letting partial lines show, which is the default. You can show only complete lines if they fit inside the layout rectanglethe behavior you get with String- FormatFlags.LineLimit. Finally, you can decide to show complete lines even if they lie outside the layout rectangle, which is what you get with StringFormatFlags.NoClip. Combining LineLimit with NoClip is not useful, because the behavior is the same as LineLimit. The three options are shown in Figure 6.5. Figure 6.5. The Effect of the LineLimit StringFormatFlags Value
String Trimming
If, on the other hand, the string is too long, you can dictate what happens by setting the Trimming property of the StringFormat object to one of the StringTrimming enumeration values: namespace System.Drawing { enum StringTrimming { None = 0, // No trimming (acts like Word for single lines) Character = 1, // Trim to nearest character (the default) Word = 2, // Trim to nearest word EllipsisCharacter = 3, // Trim to nearest character // and show ellipsis EllipsisWord = 4, // Trim to nearest word and show ellipsis EllipsisPath = 5, // Trim file path by putting ellipsis // in the middle } }
Figure 6.6 shows the results of applying the StringTrimming values when you draw a string. Figure 6.6. Examples of the StringTrimming Enumeration
Tab Stops
Something else of interest in Figure 6.6 is the use of tabs to line up the string, instead of forcing the text to be in a monospaced font and aligning the text with space characters. You set tabs using the SetTabStops method of the StringFormat class: using( StringFormat format = new StringFormat() ) { SizeF size = g.MeasureString( StringTrimming.EllipsisCharacter.ToString(), this.Font); format.SetTabStops(0, new float[] { size.Width + 10 }); }
This call to SetTabStops sets a single tab stop to be the width of the longest string, plus a pleasing amount of padding. When tab stops are specified and when StringFormatFlags.NoWrap is absent from the StringFormat object, then the tab character (ASCII 9 or "\t") causes the characters that follow to be drawn starting at the tab stop offset (unless the string has already passed that point). If the StringFormat object has not been given any tab stops, then the tab character is not interpreted. If DrawString is called without any StringFormat object at all, it builds one internally that defaults tab width to four times the size of the font; for example, a 12-point font will have tab stops every 48 points. There are several ways to specify tab stops logically. For example, imagine that you'd like a tab stop at every 48 units, as DrawString does by default when no StringFormat is provided. You might also imagine that you'd like to specify only a certain number of tab stops at specific locations. Finally, you might imagine that you'd like to have an array of tab stops but use an offset determined at run time to calculate the actual tab stops. All these techniques are supported, but you must use a single SetTabStops method, and that makes things somewhat unintuitive. The array of floating-point values passed to set the tab stops represents the spaces between successive tab stops. The first value in this array is added to the first argument to SetTabStops to get the first tab stop, and each successive value is added to the preceding value to get the next tab stop. Finally, when more tabs are found than tab stops, the last value of the array is added repeatedly to get successive tab stops. Table 6.2 shows various arguments passed to SetTabStops and the resultant offsets for each stop.
You may have noticed the GetTabStops method on the StringFormat class, but unfortunately it hands back only the same tab stop settings handed to SetTabStops in the first place. It would have been handy to get back the resultant tab stops so that you could make sure you've set them correctly. Hotkey Prefixes
In addition to new lines and tab characters, DrawString can substitute other characters, including ampersands and digits. Substitution of ampersands is a convenience for specifying Windows hotkeys for menu items and form fields. For example, by default the string "&File" is output as "&File" (but without the quotation marks). However, you can specify that the ampersand be dropped or that the next character be underlined, as governed by the HotkeyPrefix enumeration: namespace System.Drawing.Text { enum HotkeyPrefix { None = 0, // Show all & characters (default) Show = 1, // Drop & characters and underline next character Hide = 2, // Drop all characters } } For example, the following translates "&File" into "File" (no quotation marks) as the string is drawn: using( StringFormat format = new StringFormat() ) { format.HotkeyPrefix = HotkeyPrefix.Show; g.DrawString("&File", font, brush, rect, format); }
Digit Substitution
One other substitution that DrawString can perform is for digits. Most languages have adopted the Arabic digits (0, 1, 2, 3, ...) when representing numbers, but some also have traditional representations. Which representation to show is governed by the method and language, as determined by a call to the SetDigitSubstitution method on the StringFormat class: CultureInfo culture = new CultureInfo("th-TH"); // Thailand Thai using( StringFormat format = new StringFormat() ) { format.SetDigitSubstitution( culture.LCID, StringDigitSubstitute.Traditional); g.DrawString("0 1 2...", font, brush, rect, format); }
The substitution method is governed by StringDigitSubstitute (and can be discovered using the DigitSubstitutionMethod on the StringFormat class), as shown in Figure 6.7 Figure 6.7. StringDigitSubstitute Values as Applied to Thailand Thai
The integer language identifier comes from the LCID (language and culture ID) of an instance of the CultureInfo class. It can be constructed with a two-part name: a two-letter country code followed by a two-letter language code, separated by a hyphen.[7] The methods applied to the national and traditional languages of Thailand are shown in Figure 6.7 [7] The country code and language codes are defined by ISO standards. Alignment
In addition to substitution, tabs, wrapping, and clipping, you can use StringFormat to set text alignment (both horizontally and vertically) by setting the Alignment and LineAlignment properties, respectively, using one of the StringAlignment enumeration values: namespace System.Drawing { enum StringAlignment { Near = 0, // Depends on right-to-left setting Center = 1, Far = 2, // Depends on right-to-left setting } }
Notice that instead of Left and Right alignment, the StringAlignment enumeration values are Near and Far and depend on whether the RightToLeft string format flag is specified. The following code centers text in a rectangle horizontally and vertically: // Center horizontally format.Alignment = StringAlignment.Center; // Center vertically format.LineAlignment = StringAlignment.Center;
Two combinations on a StringFormat object are so commonly needed that they're set up for you and are exposed via the GenericDefault and GenericTypographic properties of the StringFormat class. The GenericDefault StringFormat object is what you get when you create a new StringFormat object, so it saves you the trouble if that's all you're after. The GenericTypographic StringFormat object is useful for showing text as text, not as part of drawing a UI element. The properties you get from each are shown in Table 6.3.
Antialiasing
All the strings I've shown in the sample figures in this section have been nice and smooth. That's because I'm using Windows XP with ClearType turned on. If I turn that off, I go back to the old, blocky way of looking at things. However, when I'm drawing strings, I don't have to settle for what the user specifies. Before I draw a string, I can set the TextRenderingHint property of the Graphics object to one of the TextRenderingHint enumeration values, as shown in Figure 6.8 Figure 6.8. Examples of the TextRenderingHint Enumeration
In this case, SystemDefault shows what text looks like without any smoothing effects. The SingleBitPerPixel setting does just what it says, although it's clearly not useful for anything that needs to look great. The AntiAlias and ClearType settings are two different algorithms for smoothing that are meant to make the text look good: one for any monitor, and one specifically for LCD displays. The grid fit versions of the algorithms use extra hints to improve the appearance, as you can see from the examples. Of course, as the quality improves, the rendering time also increases, and that's why you can set the option as appropriate for your application. Furthermore, when drawing using one of the antialiasing algorithms, you can adjust the TextContrast property of a Graphics object: for( int i = 0; i <= 12; i += 4 ) { // Set the current text contrast g.TextContrast = i; string line = string.Format("TextContrast = {0}", i.ToString()); g.DrawString(line, this.Font, Brushes.Black, 0, 0, format); ... } The contrast ranges from 0 to 12, where 0 is the most contrast and 12 is the least, with 4 being the default. The contrast makes fonts at smaller point sizes stand out more against the background. Figure 6.9 demonstrates the broad spectrum of text contrasts. Figure 6.9. Examples of the TextContrast Property
Strings and Paths
One more string-drawing trick that might interest you is the ability to add strings to graphics paths. Because everything that's added to a path has both an outline and an interior that can be drawn separately, you can add strings to a path to achieve outline effects, as shown in Figure 6.10 Figure 6.10. Using a GraphicsPath Object to Simulate an Outline-Only Font
// Need to pass in DPI = 100 for GraphicsUnit == Display GraphicsPath GetStringPath( string s, float dpi, RectangleF rect, Font font, StringFormat format) { GraphicsPath path = new GraphicsPath(); // Convert font size into appropriate coordinates float emSize = dpi * font.SizeInPoints / 72; path.AddString( s, font.FontFamily, (int)font.Style, emSize, rect, format); return path; } void OutlineFontsForm_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; string s = "Outline"; RectangleF rect = this.ClientRectangle; Font font = this.Font; float dpi = g.DpiY; using( GraphicsPath path = GetStringPath( s, dpi, rect, font, StringFormat.GenericTypographic) ) { g.DrawPath(Pens.Black, path); } }
Even though I have ClearType on and the TextRenderingHint set to SystemDefault, the outline path was not drawn smoothly. As soon as the string was used to create a path, it stopped being text and became a shape, which is drawn smoothly or not based on the SmoothingMode property. Also, notice that I showed an example of a really big font (72-point). The string-as-path trick doesn't work very well at lower resolutions because of the translation of font family characters into a series of lines and curves. Even more interesting uses of paths are available when you apply transformations, which you'll read about in Chapter 7: Advanced Drawing. |
Категории