I want to draw the following text on panel:
It is a multi-colored text.
I found this article about drawing multicolored text.
I replaced characters with words but it doesn't work.
(I use FillPath/DrawPath to draw text)
my code:
private void Form1_Paint(object sender, PaintEventArgs e)
{
const string txt = "C# Helper! Draw some text with each letter in a random color.";
// Make the font.
using (Font the_font = new Font("Times New Roman", 40,
FontStyle.Bold | FontStyle.Italic))
{
// Make a StringFormat object to use for text layout.
using (StringFormat string_format = new StringFormat())
{
// Center the text.
string_format.Alignment = StringAlignment.Center;
string_format.LineAlignment = StringAlignment.Center;
string_format.FormatFlags = StringFormatFlags.NoClip;
// Make CharacterRanges to indicate which
// ranges we want to measure.
MatchCollection mc = Regex.Matches(txt, @"[^\s]+");
CharacterRange[] ranges = new CharacterRange[mc.Count];
int g = 0;
foreach (Match m in mc)
{
ranges[g] = new CharacterRange(m.Index, m.Length);
g++;
}
string_format.SetMeasurableCharacterRanges(ranges);
// Measure the text to see where each character range goes.
Region[] regions =
e.Graphics.MeasureCharacterRanges(
txt, the_font, this.ClientRectangle,
string_format);
// Draw the characters one at a time.
for (int i = 0; i < ranges.Length; i++)
{
// See where this character would be drawn.
RectangleF rectf = regions[i].GetBounds(e.Graphics);
Rectangle rect = new Rectangle(
(int)rectf.X, (int)rectf.Y,
(int)rectf.Width, (int)rectf.Height);
// Make a brush with a random color.
using (Brush the_brush = new SolidBrush(RandomColor()))
{
// Draw the character.
string txts = txt.Substring(ranges[i].First, ranges[i].Length);
e.Graphics.DrawString(txts,
the_font, the_brush, rectf, string_format);
}
}
}
}
}
What is the problem?
This is (in a way) a classic.
There is a discrepancy between the quite precise measure performed by MeasureCharacterRanges and the actual string drawing performed by Graphics.DrawString
.
The RectagleF
returned by Region.GetBounds()
considers the measure of the Text as it is.
Graphics.DrawString
, on the other hand, performs a sort of grid-fitting when calculating a Text disposition inside the given bounds.
I won't explain it here, it's quite a broad matter, but I've written something about it already:
Drawing a Long String on to a Bitmap results in Drawing Issues
If you're interested, you can find some details on the Graphics
object behavior in this context.
The sum of it is, the Text is measured correctly, but the adjustments that Graphics.DrawString
performs, cause the Text to not fit completely in the measured bounds: the drawn Text slightly overflows.
You could correct this problem using a couple of StringFormat
flags:
Add [StringFormat].Trimming = StringTrimming.None
With this setting applied, you can immediately see what the problem is: the last char (or few chars) is wrapped to a new line, messing up the drawing.
To correct it, add StringFormatFlags.NoWrap
to StringFormatFlags.NoClip
.
This will, apparently, solve the problem. Apparently because now the whole string is drawn on a single line.
I propose you another method, using TextRenderer.DrawText to render the strings.
Note that TextRenderer
is actually the class used by the WinForms
controls (well, not all of them) to render Text to the screen.
This is the result using the method that follows:
Sample code, using your original code with some modifications:
TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter |
TextFormatFlags.NoPadding | TextFormatFlags.NoClipping;
const string txt = "C# Helper! Draw some text with each word in a random color.";
private void panel1_Paint(object sender, PaintEventArgs e) {
Control control = sender as Control;
using (var format = new StringFormat())
using (var font = new Font("Times New Roman", 40, FontStyle.Regular, GraphicsUnit.Point)) {
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
MatchCollection mc = Regex.Matches(txt, @"[^\s]+");
CharacterRange[] ranges = mc.Cast<Match>().Select(m => new CharacterRange(m.Index, m.Length)).ToArray();
format.SetMeasurableCharacterRanges(ranges);
Region[] regions = e.Graphics.MeasureCharacterRanges(txt, font, control.ClientRectangle, format);
for (int i = 0; i < ranges.Length; i++) {
Rectangle WordBounds = Rectangle.Round(regions[i].GetBounds(e.Graphics));
string word = txt.Substring(ranges[i].First, ranges[i].Length);
TextRenderer.DrawText(e.Graphics, word, font, WordBounds, RandomColor(), flags);
}
}
}
private Random rand = new Random();
private Color[] colors =
{
Color.Red,
Color.Green,
Color.Blue,
Color.Lime,
Color.Orange,
Color.Fuchsia,
};
private Color RandomColor()
{
return colors[rand.Next(0, colors.Length)];
}