Strings


Of course, deciding on a font is only half the fun. The real action is drawing strings after the font's been picked. For that, you use the DrawString method of the Graphics object:

 using( Font font = new Font("Arial", 12) ) {   // This will wrap 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 will automatically wrap long lines and   // it will wrap 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 can do so with an instance of the StringFormat class:

 sealed class StringFormat :   MarshalByRefObject, ICloneable, 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(ref Single 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:

 // Turn off auto-wrapping  StringFormat format = new StringFormat(StringFormatFlags.NoWrap);  g.DrawString("...", font, brush, rect,  format  ); 

The StringFormatFlags enumeration provides a number of options:

 enum StringFormatFlags {   0, // No flags (default)   DirectionRightToLeft, // Draw text right-to-left   DirectionVertical, // Draw text up-to-down   DisplayFormatControl, // Show format control character glyphs   FitBlackBox, // Keep all glyphs inside layout rectangle   LineLimit, // Show only whole lines   MeasureTrailingSpaces, // MeasureString includes trailing spaces   NoClip, // Don't clip text partially outside layout rectangle   NoFontFallback, // Don't fall back for characters missing from Font   NoWrap, // Don't interpret \n or \t (implied when no rect provided) } 

Note that a glyph is a symbol that conveys some kind of information. For example, the X in the upper-right corner of a window is a glyph that indicates that the window can be closed.

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 down-to-up and disables automatic wrapping:

 StringFormat format = new StringFormat(); format.FormatFlags =   StringFormatFlags.DirectionVertical  StringFormatFlags.NoWrap; g.DrawString("...", font, brush, rect, format); 

When text doesn't fit into the allotted space, you have a few options. If the string is too tall, 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 rectangle, which is the behavior you get with StringFormatFlags.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 5.3.

Figure 5.3. The Effect of the LineLimit StringFormatFlags Value

String Trimming

If, on the other hand, the string is too long, you can decide what happens by setting the Trimming property of the StringFormat object to one of the StringTrimming enumeration values:

 enum StringTrimming {   None, // No trimming (acts like Word for single lines)   Character, // Trim to nearest character (the default)   EllipsisCharacter, // Trim to nearest character and show ellipsis   Word, // Trim to nearest word   EllipsisWord, // Trim to nearest word and show ellipsis   EllipsisPath, // Trim file path by putting ellipsis in the middle } 

Figure 5.4 shows the results of applying the StringTrimming values when you draw a string.

Figure 5.4. Examples of the StringTrimming Enumeration

Tab Stops

Something else of interest in Figure 5.4 is the use of tabs to line up the string, instead of forcing the text to be in a monospaced font and lining up the text with spaces. Tabs are set using the SetTabStops method of the StringFormat class:

 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 leading. When tab stops are specified and when StringFormatFlags.NoWrap is absent from the StringFormat object, then the tab character 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 will not be interpreted. If DrawString is called without any StringFormat object at all, it will build one internally that defaults 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 5.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.

Table 5.2. Sample Arguments to SetTabStop and Resultant Tab Stops

Arguments to SetTabStop

Resultant Tab Stops

Description

0, { 100 }

100, 200, 300,

Tab stops every 100 units

0, { 100, 0 }

100, 100, 100,

One tab stop at 100 units

0, { 50, 75, 100 }

50, 125, 225, 325, 425,

A tab stop at 50, 125, and 225 and then one every 100 units

0, { 50, 75, 100, 0 }

50, 125, 225, 225, 225,

A tab stop at 50, 125, and 225 units

50, { 100 }

150, 250, 350,

One tab stop at 150 units and then one every 100 units

50, { 100, 0 }

150, 150, 150,

One tab stop at 150 units

50, { 50, 75, 100 }

100, 175, 275, 375, 475,

A tab stop at 100, 175, and 275 and then one every 100 units

50, { 50, 75, 100, 0 }

100, 175, 275, 275, 275,

A tab stop at 100, 175, and 275 units

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" will be 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 from the System.Drawing.Text namespace:

 enum HotkeyPrefix {   Hide, // Drop all & characters   None, // Show all & characters (default)   Show, // Drop & characters and underline next character } 

For example, the following translates "&File" into " F ile" (no quotation marks) as the string is drawn:

 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 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 the StringDigitSubstitute (and can be discovered with the DigitSubstitutionMethod on the StringFormat class), as shown in Figure 5.5.

Figure 5.5. 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, [5] separated by a hyphen. The methods applied to the national and traditional languages of Thailand are shown in Figure 5.5.

[5] 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:

 enum StringAlignment {   Center,   Near, // Depends on right-to-left setting   Far, // 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 centers text in a rectangle both horizontally and vertically:

 format.Alignment = StringAlignment.Center; // Center horizontally format.LineAlignment = StringAlignment.Center; // Center vertically 

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 5.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. I can set the TextRenderingHint property of the Graphics object before I draw a string to one of the TextRenderingHint enumeration values, as shown in Figure 5.6.

Figure 5.6. Examples of the TextRenderingHint Enumeration

Table 5.3. The Settings of the Built-in StringFormat Classes

GenericDefault

GenericTypographic

StringFormatFlags = 0

StringFormatFlags = LineLimit, NoClip

Alignment = Near

Alignment = Near

LineAlignment = Near

LineAlignment = Near

DigitSubstitutionMethod = User

DigitSubstitutionMethod = User

HotkeyPrefix = None

HotkeyPrefix = None

No tab stops

No tab stops

Trimming = Character

Trimming = None

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 decent. 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. 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.

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 5.7:

 // 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;   StringFormat format = StringFormat.GenericTypographic;   float dpi = g.DpiY;  using( GraphicsPath path =   GetStringPath(s, dpi, rect, font, format) ) {   g.DrawPath(Pens.Black, path);   }  } 
Figure 5.7. Using a GraphicsPath Object to Simulate an Outline-Only Font

Notice that 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, you'll 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 transforms, which you'll read about in Chapter 6: Advanced Drawing.



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