In a graphical world, text is treated as a graphical
object. This has some nice benefits. You can freely mix text and
graphical images together. You can also use several fonts together to
create a richer style of output. The drawback to this added power is
that some effort is required to coordinate the placement of the various
graphical objects in the available drawing area. By contrast, a
console-style application can send its text to an output window,
where automatic word wrapping and automatic scrolling take the worry
out of creating text output. In a graphical environment, you—the
programmer—must measure the space that text occupies. The reward for
this effort is total control in text placement.
Even if you are drawing
only text, with no other kind of graphical object, you still have some
work to do. For example, drawing multiple lines of text requires you to
calculate the height of each line of text so that you can position each
line the correct distance from the previous line. For this and other
text-measuring tasks, the .NET Compact Framework provides the MeasureString method, which resides in the Graphics class. We discuss this next.
1. Text Size and the MeasureString Method
Most people have looked at
text their entire lives. Yet, using text to read and write is not the
same as managing the display of graphical text. If you look closely at
individual characters—for example, the letter G—you
see a very complicated piece of artwork that a font designer spent many
hours perfecting. To efficiently deal with text without getting lost in
each glyph, graphics programmers simplify a text string.
We handle graphical text in
terms of an imaginary bounding box that surrounds the text string. To
place graphical text, we organize the placement of the bounding box
within the drawing area, relative to other text and nontext graphical
objects. This simplification is the basis of all efforts to measure the
size of text in a graphics system.
The MeasureString method returns the width and height of a text string by providing a SizeF structure. The F
at the end of the structure name indicates that the structure contains
floating-point values (a Hungarian-like suffix). For our purposes, SizeF has two interesting properties:
public float Width {get; set;}
public float Height {get; set;}
The MeasureString method returns the height and width for a specified string drawn with a given font. To calculate the bounding box, we specify both the character string and the font to be used in drawing the string. Here is the definition of the MeasureString method:
public SizeF MeasureString(
string text,
Font font);
2. A Sample Program: MeasureString
The MeasureString
method is simple, so the accompanying sample program is also simple.
The greatest challenge is that the bounding box size is always provided
as floating-point values. You must create conversion logic if your
text-drawing code interacts with other code that uses integer
coordinates. Among other things, this includes mouse input coordinates
and the coordinates for drawing vector or raster graphics.
A text box allows the user to type in some text. As shown in Figure 1,
the sample draws white text on a black background to highlight the size
and placement of the bounding box. We also went a little crazy with
some line drawing to illustrate the relationship between the reported
height and width and the resulting text. Listing 1 provides a fragment of the code in the Paint event handler for measuring and drawing the string.
Listing 1. Fragment Showing the Call to the MeasureString Method
string str = this.Text;
Brush brFore = new SolidBrush(SystemColors.Window);
Brush brBack = new SolidBrush(SystemColors.WindowText);
// Upper-left corner of text box.
float sinX = (float)(labelWidth.Right/5);
float sinY = (float)(labelHeight.Bottom + 12);
SizeF szf = e.Graphics.MeasureString(str, Font);
// Draw rectangle in text background.
int xRect = (int)sinX;
int yRect = (int)sinY;
int cxRect = (int)szf.Width;
int cyRect = (int)szf.Height;
Rectangle rc;
rc = new Rectangle(xRect, yRect, cxRect, cyRect);
e.Graphics.FillRectangle(brBack, rc);
// Draw string.
e.Graphics.DrawString(str, Font, brFore, sinX, sinY);
|
The bounding box for the text is visible in Figure 12.6. When we specify the location for the text, the drawing coordinate in the DrawString method corresponds with the upper-left corner of the bounding box. Our next sample program, TextAlign, shows how to get eight other alignments.
You might notice that the
top of the bounding box contains a blank horizontal area. In the world
of digital typography, this is referred to as the internal leading. The
term leading
comes from typesetting as done in the predigital era when cubes holding
character images had to be individually placed to form lines of text.
Using this printing technology, a typesetter placed a long, thin piece
of metal—known as the leading—between the rows of letters to provide the
interline spacing. In the world of digital fonts, leading is split into
two parts: the internal leading and the external leading. These two
names indicate whether or not the leading is included in the reported
height.
The interline spacing that the MeasureString
method reports works reasonably well in most cases, especially given
the small screen size of a typical device such as a Pocket PC that runs
.NET Compact Framework programs. Programmers who work in depth with
digital font technology may notice that absolute precision in text
placement requires an additional element, the external leading value.
For many TrueType fonts at smaller sizes, the external leading is zero
and so can often be ignored.
3. Text Alignment
The .NET Compact Framework supports four overloads to the DrawString
method. One overload positions the text with an (x,y) coordinate pair.
We use this overload in all the samples presented up to this point. The
second overload positions the text within a rectangle, and it performs
word wrapping of the text and clipping to the rectangle.
When text is drawn with the coordinate-pair version of the DrawString
method, the text is drawn below and to the right of the point. In other
words, the default text alignment is to the top and left of the text
box. For example, in Figure 2, the text coordinate is the intersection of the vertical and horizontal lines.
This approach has been
part of every version of Windows since Microsoft Windows 1.01 shipped in
1985. It is likely an outgrowth of the fact that the default origin of a
drawing surface is the upper-left corner, which no doubt results from
the convention in European languages of reading and writing from left to
right and from top to bottom.
Whatever the origin,
the default alignment is not always the most convenient. This alignment
does not help when you try to center some text within an object (e.g., a
rectangle or a window). Nor does this default alignment help center
text over a column of numbers.
The .NET Compact
Framework does not provide automatic text alignment. For other
alignments, you calculate an offset from the desired point to the
upper-left corner of the text box.
4. A Sample Program: TextAlign
The TextAlign
sample program lets you visualize the nine possible text alignments.
There are nine possible alignments because there are three vertical
alignment values (near, center, and far) and three horizontal alignment
values (near, center, and far). The default for text alignment, shown in
Figure 3, is near and near, which corresponds to the text drawn with the upper-left corner of the text box at the reference point indicated in the DrawString call.
There is an alternative to fussing with these alignment values: You can call one of the versions of the DrawString
function which accepts the bounding rectangle as input. It is nice to
have choices, which is a key benefit of having overloaded functions in
an object-oriented programming language.
Listing 2 shows the code for the TextAlign sample program.
Listing 2. TextAlign Program Showing How to Support Nine Text Alignment Settings
public partial class formMain : Form
{
// Text alignment flags.
StringAlignment valign;
StringAlignment halign;
// Adjustment for bounding box.
private int m_cxAdjust = 0;
private int m_cyAdjust = 0;
public formMain()
{
InitializeComponent();
// Set initial alignment values.
valign = StringAlignment.Near;
halign = StringAlignment.Near;
}
private void mitemExit_Click(object sender, EventArgs e)
{
Close();
}
private void
mitemAlignHorizontal(object sender, EventArgs e)
{
var mi = (MenuItem)sender;
// Set label to menu name.
labHorz.Text = mi.Text;
// Calculate size of string bounding box.
Graphics g = CreateGraphics();
SizeF size = g.MeasureString(this.Text, this.Font);
g.Dispose();
// Set flag and
// Calculate bounding box offset.
if (mi.Text == "Near")
{
halign = StringAlignment.Near;
m_cyAdjust = 0;
}
else if (mi.Text == "Center")
{
halign = StringAlignment.Center;
m_cyAdjust = (int)(size.Height / 2);
}
else if (mi.Text == "Far")
{
halign = StringAlignment.Far;
m_cyAdjust = (int)size.Height;
}
// Force a redraw.
Invalidate();
}
private void
mitemAlignVertical(object sender, EventArgs e)
{
var mi = (MenuItem)sender;
// Set label to menu name.
labVert.Text = mi.Text;
// Calculate size of string bounding box.
Graphics g = CreateGraphics();
SizeF size = g.MeasureString(this.Text, this.Font);
g.Dispose();
// Set flag and
// Calculate bounding box offset.
if (mi.Text == "Near")
{
valign = StringAlignment.Near;
m_cxAdjust = 0;
}
else if (mi.Text == "Center")
{
valign = StringAlignment.Center;
m_cxAdjust = (int)(size.Width / 2);
}
else if (mi.Text == "Far")
{
valign = StringAlignment.Far;
m_cxAdjust = (int)size.Width;
}
// Force a redraw.
Invalidate();
}
private void
formMain_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
// Calculate reference point for text drawing.
float xDraw = (float)(ClientRectangle.Right / 2);
float yDraw = (float)(ClientRectangle.Bottom / 2);
// Create drawing objects.
Brush brBlack = new SolidBrush(Color.Black);
Pen penBlack = new Pen(Color.Black);
// Create StringFormat for text drawing details.
var sf = new StringFormat();
sf.FormatFlags = StringFormatFlags.NoClip;
sf.Alignment = valign;
sf.LineAlignment = halign;
// Calculate size of string bounding box.
SizeF size = g.MeasureString(this.Text, Font);
int cxWidth = (int)size.Width;
int cyHeight = (int)size.Height;
// Adjust values to accommodate alignment request.
float sinTextX = (float)(xDraw - m_cxAdjust);
float sinTextY = (float)(yDraw - m_cyAdjust);
// Draw text bounding box.
Brush hbrFill = new SolidBrush(Color.Gray);
Rectangle rc = new Rectangle((int)sinTextX,
(int)sinTextY,
cxWidth,
cyHeight);
g.FillRectangle(hbrFill, rc);
// Draw text string.
g.DrawString(this.Text, Font, brBlack, xDraw,
yDraw, sf);
// Draw reference cross-hairs.
g.DrawLine(penBlack, (int)xDraw, 0, (int)xDraw,
this.Height);
g.DrawLine(penBlack, 0, (int)yDraw, this.Width,
(int)yDraw); }