rawing text in Visual Basic 6 was all very simple?both the process and the results. First, you would set a form or PictureBox’s CurrentX and CurrentY properties and then call the Print method.
In contrast, .NET provides more powerful tools for drawing text. As is so often the case however, the extra power comes with extra complexity. While you can do amazing things with text in .NET, there’s a lot more to learn. Still, you can attack the complexity in easily-digestible chunks, beginning with simple text display, then gradually adding new “tricks” such as changing fonts and alignment, clipping, wrapping, shading, and transforming text, until you’re thoroughly conversant with .NET’s text-display features.
The sample code for this article consists of a set of small applications that illustrate all these techniques. You can download the code and follow along, or use it as template or starter code for your own applications. Although I’ve used VB.NET for the examples in this article, the downloadable code provides both VB.NET and C# versions.
.NET Text Basics
Whenever you draw text in a .NET application, you need a Graphics object. This object provides the tools you need to draw lines, polygons, curves, images, and of course text. The DrawString method draws text.
One of the really nice things about .NET graphics is that you use a Graphics object to draw on anything, whether that’s a form, PictureBox, owner-drawn menu, printer, or something even more exotic. That means you need to learn how to draw only once; after that, the process is similar for any other drawing operation on any surface.
The examples in this article draw during a form’s Paint event handler. That event handler’s e.Graphics parameter gives you the graphics object that you need to draw.
While .NET’s text drawing tools can be complicated, drawing text isn’t too hard in its simplest form. For example, you can place the following code inside a form’s Paint event handler. This code draws the text “BasicText” at the point (10, 10) measured in pixels from the upper left corner of the form. The text is blue and is drawn in the form’s current font. This code is in VB.NET but the downloadable sample code includes both VB.NET and C# versions.
e.Graphics.DrawString("BasicText", Me.Font, Brushes.Blue, 10, 10)
This code is extremely easy to use. By changing the form’s font at design time, you can draw text in whatever font you like. The only catch is that all of the text you draw will use the same font.
To work around that problem, the following slightly more complicated version builds its own font for just one piece of text. It starts with a Using statement that creates a bold 24-point Comic Sans MS font. The Using statement makes VB.NET call the font’s Dispose method automatically when it reaches the End Using statement. After creating the font, the code calls DrawString much as the previous code doe except this time it passes in the new font.
Using the_font As New Font("Comic Sans MS", 24, FontStyle.Bold) e.Graphics.DrawString("BasicText", the_font, _ Brushes.Blue, 10, 20) End Using
Figure 1 shows the results of these two calls to DrawString.
DrawString handles special characters such as carriage returns and tabs well. Figure 2 shows the results of executing drawing code similar to the previous examples, but that draws text containing a carriage return/line feed pair and a tab. The VB.NET example draws the string, “The quick brown fox” & vbCrLf & “jumps over” & vbTab & “the lazy dog.” The C# version draws the string “The quick brown fox
jumps over the lazy dog.”
The previous examples passed the DrawString method X and Y coordinates to mark the upper left corner of the drawn text (that’s the way Visual Basic 6 worked, too) but you can also pass a Point instead of separate X and Y values, or you can pass a Rectangle in which DrawString will draw the text.
Unfortunately passing the upper left corner’s coordinates into the routine makes it hard to align text. For example, if you want to right-justify text, you would need to figure out how wide the string would be when drawn and then set the X coordinate to the right margin minus the string’s width. To center the text, you would need to subtract half of the string’s drawn width from the coordinate where you wanted the text centered. You would need to perform similar calculations to align or center the text vertically.
While many of us have been performing these calculations for years, the DrawString method provides an easier way to align text. Several overloaded versions of DrawString take a StringFormat object as a parameter. The StringFormat object’s Alignment and LineAlignment properties determine the string’s horizontal and vertical alignment respectively. You can set Alignment and LineAlignment to Near, Center, or Far to make DrawGraphics put the text on the left/top, center, or right/bottom of the starting point.
|Figure 3. Alignment Using StringFormat: The StringFormat object determines how text is aligned with respect to its reference point.|
As an example, the following code draws three lines of text centered horizontally. It starts by defining X and Y coordinates for a point and calling FillEllipse so you can see the text’s reference point. Next it creates a StringFormat object in a Using statement, setting its Alignment property to Center to center the text horizontally, and its LineAlignment property to Near to draw the text below the point. Finally it calls DrawString, passing it parameters similar to those used in the previous examples with the StringFormat object added at the end. Figure 3 shows the result.
Dim x As Integer = 150 Dim y As Integer = 20 e.Graphics.FillEllipse(Brushes.Red, x - 2, y - 2, 5, 5) Using string_format As New StringFormat() string_format.Alignment = StringAlignment.Center string_format.LineAlignment = StringAlignment.Near e.Graphics.DrawString( _ "Visual Basic" & vbCrLf & _ "Makes string formatting easy" & vbCrLf & _ "C# does also", _ Me.Font, Brushes.Blue, x, y, string_format) End Using
Perhaps a more intuitive way to draw text is to pass DrawString a formatting rectangle rather than X and Y coordinates or a Point. Then you can use the StringFormat object’s Alignment and LineAlignment properties to control the text’s position within the rectangle. The following code draws text aligned in the lower right corner of a rectangle with upper left corner (10, 10) that is 175 pixels wide and 70 pixels tall.
|Figure 4. Alignment Using Rectangles: The StringFormat object can align text within a formatting rectangle.|
layout_rect = New Rectangle(10, 10, 175, 70) string_format.Alignment = StringAlignment.Far string_format.LineAlignment = StringAlignment.Far e.Graphics.DrawString("Near,Near", Me.Font, _ Brushes.Blue, layout_rect, string_format)
You’ll find the complete code in the example program StringAlignment in the downloadable code. Figure 4 shows the results of drawing sample strings showing each of the nine combinations of Alignment and LineAlignment values. The sample text gives the Alignment and LineAlignment values. For example, the text in the upper right was drawn with Alignment = Far and LineAlignment = Near.
Clipping and Wrapping Text
The StringFormat object’s FormatFlags property controls how DrawString wraps and clips text within a formatting rectangle. Table 1 shows the FormatFlags values that you can use to control clipping and wrapping.
|Figure 5. Clipping and Wrapping Text: The StringFormat object’s FormatFlags property determines how text is wrapped and clipped.|
|Table 1. FormatFlags Values: The table lists the possible FormatFlags values and provides a brief description of the way each value affects text output.|
|FitBlackBox||Lines are wrapped normally. Lines that extend below the formatting rectangle are clipped?so they may be only partially drawn.|
|LineLimit||Most lines are wrapped normally. If the text contains more lines than will fit, the final line is clipped on a character boundary so a partial word may be visible.|
|NoClip||Lines are wrapped as usual and may continue below the bottom of the formatting rectangle.|
|NoWrap||The text is not wrapped. Instead, the method clips any lines that extend beyond the edge of the formatting rectangle.|
Figure 5 shows the effects of the FormatFlags values.
The StringFormat object’s Trimming property also plays a role in how DrawString clips text. The property accepts a StringTrimming enumeration value. Table 2 lists the possible values of the StringTrimming enumeration.
|Figure 6. Trimming Strings: Setting the StringFormat.Trimming property to EllipsisCharacter draws the text trimmed and adds an ellipsis at the end.|
|Table 2. StringTrimming Enumeration Values: The table lists the possible StringTrimming values and provides a brief description of the way each value affects text output.|
|Character||Text is trimmed after the last character that fits.|
|EllipsisCharacter||Text is trimmed after the last character that fits and is then followed by an ellipsis.|
|EllipsisPath||Text in the middle of the string is replaced with an ellipsis to make the string fit. This is particularly useful for file paths where you want to show the beginning and end of the path.|
|EllipsisWord||Text is trimmed after the last word that fits and is then followed by an ellipsis.|
|None||Text is not trimmed.|
|Word||Text is trimmed after the last word that fits.|
For example, the following code makes the StringFormat object clip the text after the last word that will fit and add an ellipsis at the end.
string_format.Trimming = StringTrimming.EllipsisWord
The example program Ellipses demonstrates each of the StringTrimming values. Figure 6 shows the sample program output with StringFormat.Trimming set to EllipsisCharacter.
DrawString draws text using a normal, unmangled font. It won’t stretch, rotate, or otherwise contort the font. The Graphics object that provides the DrawString method, however, also provides methods for rotating, translating, and scaling whatever it draws?including text. The Graphics object’s TranslateTransform, ScaleTransform, and RotateTransform methods move, stretch, and rotate the drawing.
|Figure 7. Transforming Text: The Graphics object can transform text, stretching it horizontally and/or vertically, and translating it to a different location.|
For example, the following code draws text that has been stretched vertically. It starts by finding the form’s center (cx, cy). It then uses the ScaleTransform method to scale the drawing horizontally by a factor of one (leaving the horizontal size unchanged) and vertically by a factor of six.
Finally, it adds a translation of distance cx in the horizontal direction and cy in the vertical direction. That moves the drawn objects around the point of origin so they are centered in the form. Notice that the final parameter to TranslateTransform is Drawing2D.MatrixOrder.Append. That makes the Graphics object apply the translation after it applies the scaling. By default (for some odd reason), these routines perform their transformations before any previous transformations and it’s important to perform them in the proper order. In general, transformations applied in reverse sequence do not produce the same result.
After setting up the Graphics object’s transformation, the code draws some text centered at the origin. The transformation stretches it and moves it to the center of the form. Figure 7 shows the result.
Dim cx As Integer = Me.ClientSize.Width 2 Dim cy As Integer = Me.ClientSize.Height 2 e.Graphics.ScaleTransform(1, 6, Drawing2D.MatrixOrder.Append) e.Graphics.TranslateTransform(cx, cy, _ Drawing2D.MatrixOrder.Append) Using string_format As New StringFormat() string_format.Alignment = StringAlignment.Center string_format.LineAlignment = StringAlignment.Center e.Graphics.DrawString("StretchedText", Me.Font, _ Brushes.Blue, 0, 0, string_format) End Using
The Graphics object’s RotateTransform method rotates anything you draw around the origin. Often it is more convenient to rotate around a point other than the origin. While RotateTransform won’t do that for you, it’s easy enough to build a rotation around some other point by first translating that point to the origin, performing the rotation, and then translating the point back to its original location.
The RotateAt method shown in the following code makes this process easy. It prepares a Graphics object to rotate around an arbitrary point.
|Figure 8. Rotating Text: This output shows month names drawn using the RotateAt method, which rotates text around an arbitrary point.|
' Prepare the Graphics object to rotate ' at the indicated point. Private Sub RotateAt( _ ByVal gr As Graphics, _ ByVal cx As Integer, _ ByVal cy As Integer, _ ByVal angle As Single) gr.ResetTransform() gr.TranslateTransform(-cx, -cy, _ Drawing2D.MatrixOrder.Append) gr.RotateTransform(angle, _ Drawing2D.MatrixOrder.Append) gr.TranslateTransform(cx, cy, _ Drawing2D.MatrixOrder.Append) End Sub
The example program RotatedText, shown in Figure 8, uses the RotateAt method to draw rotated month names above a grid.
The Graphics object provides two main groups of graphical routines: those that fill shapes and those that draw them. For example, DrawRectangle outlines a rectangle while FillRectangle fills one. The drawing routines take Pen objects as parameters to control how to draw the lines. Similarly, you pass Brushes to the filling routines to control how to fill areas.
Even though the DrawString method begins with the word “Draw,” it actually fills the text with a brush. So far, the examples you’ve seen have used solid brushes but there’s no reason you can’t use more exotic brush classes such as TextureBrush, HatchBrush, and LinearGradientBrush.
For example, the following code draws text shaded with a LinearGradientBrush. It finds the center of the form and sets up a StringFormat object to center text over its reference point. It then uses the Graphics object’s MeasureString function to find out how big the text will be when it is drawn by the Graphics object using the form’s font. The code uses this size to find the corners of the rectangle that will hold the text. It makes a brush to fill the rectangle with a vertical gradient and draws the text with the brush. Finally the code draws the rectangle.
' Find the center of the form. Dim origin As New PointF( _ Me.ClientSize.Width 2, _ Me.ClientSize.Height 2) Const TXT As String = "ShadedText" Using string_format As New StringFormat() ' See how big the text will be using the form's font. string_format.Alignment = StringAlignment.Center string_format.LineAlignment = StringAlignment.Center Dim txt_size As SizeF = e.Graphics.MeasureString( _ TXT, Me.Font, origin, string_format) ' Make a brush that shades vertically. Dim rect As New Rectangle( _ origin.X - txt_size.Width 2, _ origin.Y - txt_size.Height 2, _ txt_size.Width, _ txt_size.Height) Using br As New LinearGradientBrush( _ rect, Color.Red, Color.Blue, _ LinearGradientMode.Vertical) e.Graphics.DrawString(TXT, Me.Font, br, _ origin, string_format)
End Using ' br e.Graphics.DrawRectangle(Pens.Red, rect) End Using ' string_format
? Figure 9. Filling Text: You can fill text with any brush, including a LinearGradientBrush, as shown in this figure.
Figure 9 shows the result. Notice that the rectangle surrounding the text contains some extra space around the edges.
Visual Studio .NET gives you a complete set of powerful tools for drawing text. You can draw text with different fonts at different locations, control clipping, wrapping, and alignment, transform text by stretching or rotating it, and shade and fill it using interesting brushes. Drawing cleverly shaded text that’s rotated, stretched, and positioned in exactly the right location can be a bit of work, but with a little practice, your applications will be able to perform text tricks on command.