String drawing gives me headache... advise very welcome

F

Fre

Hi all,

I'm working on this for some time now, but it keeps giving me
headaches. I'm drawing strings in a label and in a
DataGridViewTextBoxCell. Why? Subparts of the string need other
colors. It's working, but not as should be. I'm not able to fully
understand how drawing works with TextFormatFlags and StringFormat.
I've the feeling I tried all possible combinations of flag settings,
but it's not working as should be (which is well formatted strings and
backgrounds, well formed spacing...). This is a complete example that
draws a string in a label (to test, create a project with a Form
(Form1) and a Label (label1). As you can see after testing the code,
the result is buggy. How does MS draws the strings?

using System;
using System.Drawing;
using System.Windows.Forms;

namespace TestDrawing
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Size = new Size(600, 80);
label1.Location = new Point(5, 5);
label1.Size = new Size(585, 21);
}

private void label1_Paint(object sender, PaintEventArgs e)
{
Rectangle rec = e.ClipRectangle;
Graphics g = e.Graphics;
Font f = new Font("Courier New", 10, FontStyle.Regular);
string[] writeThis = { "This drawing ",
"gives",
" me ",
"a",
" serious ",
"headache",
"! Why",
" isn't it ",
"w",
"orkin",
"g?"
};

// text format flags
TextFormatFlags flags = TextFormatFlags.Left;
flags |= TextFormatFlags.NoPadding;
flags |= TextFormatFlags.PreserveGraphicsClipping;
flags |= TextFormatFlags.NoPrefix;
flags |= TextFormatFlags.GlyphOverhangPadding;

// more text layout
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Center;
sf.Trimming = StringTrimming.None;
sf.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
sf.FormatFlags |= StringFormatFlags.NoWrap;
sf.FormatFlags |= StringFormatFlags.NoFontFallback;

bool b = false;
Size propsize = new Size(int.MaxValue, int.MaxValue);

foreach (string s in writeThis)
{
Size size = TextRenderer.MeasureText(g, s, f, propsize,
flags);
rec.Width = size.Width;

// fill rectangle and draw string (toggle colors)
b = !b;
g.FillRectangle(b ? Brushes.Indigo : Brushes.LightGreen, rec);
g.DrawString(s, f, b ? Brushes.Wheat : Brushes.Black, rec,
sf);

// update the start position of our rectangle
rec.X = rec.X + size.Width;
}
}
}
}

Thanks in advance for your feedback.
Frederik
 
F

Family Tree Mike

Fre said:
Hi all,

I'm working on this for some time now, but it keeps giving me
headaches. I'm drawing strings in a label and in a
DataGridViewTextBoxCell. Why? Subparts of the string need other
colors. It's working, but not as should be. I'm not able to fully
understand how drawing works with TextFormatFlags and StringFormat.
I've the feeling I tried all possible combinations of flag settings,
but it's not working as should be (which is well formatted strings and
backgrounds, well formed spacing...). This is a complete example that
draws a string in a label (to test, create a project with a Form
(Form1) and a Label (label1). As you can see after testing the code,
the result is buggy. How does MS draws the strings?

I tried your code and I don't think I understand where you think there is a
problem. The code seems to draw the strings as you have specified
(alternating fg/bg colors).
 
P

Peter Duniho

Hi all,

I'm working on this for some time now, but it keeps giving me
headaches. I'm drawing strings in a label and in a
DataGridViewTextBoxCell. Why? Subparts of the string need other
colors. It's working, but not as should be.

Please be more specific about "not as should be". What, exactly, is the
code not doing that you want it to do? Is this simply a matter of the
text you're drawing not matching exactly with the text that the Label
control itself draws? Or something else?
[...] How does MS draws the strings?

IMHO, it's futile to try to match the built-in text rendering exactly.
Microsoft's own string rendering doesn't necessarily even go through the
..NET classes, and even if it did there are so many different options that
you'd have to match exactly that finding the exact magic combination is a
waste of time. Especially since there's no contract in the API that says
Microsoft will always draw their strings in that way. As soon as they
adjust their text rendering, all that hard work is negated.

If you need for the text to be drawn exactly as Microsoft's own control
code does, then use Microsoft's own control code. Otherwise, I think it's
better to just draw the text in a way that is otherwise acceptable to you
and forget about trying to match pixel for pixel the output from the
built-in controls.

Pete
 
F

Frederik

Hi Pete and Mike,

Thanks for your feedback.
Please be more specific about "not as should be". What, exactly, is the
code not doing that you want it to do? Is this simply a matter of the
text you're drawing not matching exactly with the text that the Label
control itself draws? Or something else?

Sorry about that. Ok, so I want a string in both a Label and a
DataGridViewTextBoxCell. Some parts of the string are special and
should be marked. I implemented this by (re)drawing the string (as in
the example - sort like code is used when redrawing the
DataGridViewTextBoxCell). My purpose is that the string looks the same
as if I set the Text property of the Label, but with the difference of
having those special words marked with more eye-catching colors. What
I notice however - and I hoped the example would show this - is that
some characters are sometimes (partly) missing, or sometimes spaces
are hardly visible (or too wide). I do not want exactly the same
result as MS produces, but when spaces are missing the string becomes
less readable and that's not acceptable for me. At least on my
computer, the result of the above example is not so brilliant. So I
thought I implemented things in a wrong way and asked over here. If
you have other suggestions to get the results I want (maybe HTML or
rich text are possibilities), I'm very interested in hearing this.

HTH to clarify the problem,
Fre


I'm working on this for some time now, but it keeps giving me
headaches. I'm drawing strings in a label and in a
DataGridViewTextBoxCell. Why? Subparts of the string need other
colors. It's working, but not as should be.

Please be more specific about "not as should be". What, exactly, is the
code not doing that you want it to do? Is this simply a matter of the
text you're drawing not matching exactly with the text that the Label
control itself draws? Or something else?
[...] How does MS draws the strings?

IMHO, it's futile to try to match the built-in text rendering exactly.
Microsoft's own string rendering doesn't necessarily even go through the
.NET classes, and even if it did there are so many different options that
you'd have to match exactly that finding the exact magic combination is a
waste of time. Especially since there's no contract in the API that says
Microsoft will always draw their strings in that way. As soon as they
adjust their text rendering, all that hard work is negated.

If you need for the text to be drawn exactly as Microsoft's own control
code does, then use Microsoft's own control code. Otherwise, I think it's
better to just draw the text in a way that is otherwise acceptable to you
and forget about trying to match pixel for pixel the output from the
built-in controls.

Pete
 
P

Peter Duniho

[...] What
I notice however - and I hoped the example would show this - is that
some characters are sometimes (partly) missing, or sometimes spaces
are hardly visible (or too wide). I do not want exactly the same
result as MS produces, but when spaces are missing the string becomes
less readable and that's not acceptable for me.

Ah, okay. I understand what you're asking about now.

The basic issue is that you're using TextRenderer.MeasureText() to measure
the text, rather than calling Graphics.MeasureString(). Alternatively,
the basic issue is that you're using Graphics.DrawString() to draw the
text, rather than TextRenderer.DrawText().

In other words, you're trying to mix apples and oranges.

Either one is probably okay, but TextRenderer seems more accurate, at
least with my brief test. Using the Graphics methods, you still wind up
with a bit of margin around each drawn string, which throws off the
spacing a little. TextRenderer allows you to specify no padding, and so
it draws multiple strings in the same location (I think) as if it was just
one string.

However, either way, as long as you're consistent about how you're
measuring the text, no characters get cut off or anything like that.

Here's a modified version of your Paint event handler that behaves better:

private void label1_Paint(object sender, PaintEventArgs e)
{
Label labelSender = (Label)sender;
Rectangle rect = labelSender.DisplayRectangle;
Graphics gfx = e.Graphics;
Font font = new Font("Courier New", 10, FontStyle.Regular);
string[] writeThis = { "This drawing ",
"gives",
" me ",
"a",
" serious ",
"headache",
"! Why",
" isn't it ",
"w",
"orkin",
"g?"
};

// text format flags
TextFormatFlags flags = TextFormatFlags.Left;
flags |= TextFormatFlags.NoPadding;
flags |= TextFormatFlags.PreserveGraphicsClipping;
flags |= TextFormatFlags.NoPrefix;
flags |= TextFormatFlags.GlyphOverhangPadding;

// more text layout
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Center;
format.Trimming = StringTrimming.None;
format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
format.FormatFlags |= StringFormatFlags.NoWrap;
format.FormatFlags |= StringFormatFlags.NoFontFallback;

bool fIndigoBackground = false;
Size propsize = new Size(int.MaxValue, int.MaxValue);

rect.Width = 0;

foreach (string strCur in writeThis)
{
//SizeF size = gfx.MeasureString(strCur, font, new
PointF(rect.X, rect.Y), format);
Size size = TextRenderer.MeasureText(gfx, strCur, font,
propsize, flags);

rect.X += rect.Width;
rect.Width = (int)(size.Width + 0.5);

// fill rectangle and draw string (toggle colors)
fIndigoBackground = !fIndigoBackground;
gfx.FillRectangle(fIndigoBackground ? Brushes.Indigo :
Brushes.LightGreen, rect);
//gfx.DrawString(strCur, font, fIndigoBackground ?
Brushes.Wheat : Brushes.Black, rect, format);
TextRenderer.DrawText(gfx, strCur, font, rect,
fIndigoBackground ? Color.Wheat : Color.Black,
fIndigoBackground ? Color.Indigo : Color.LightGreen,
flags);
}
}

You can alternately comment/uncomment the TextRenderer/Graphics lines to
compare the two behavior. Of course, make sure that comment/uncomment in
pairs so that you're always measuring the text the same way you're drawing
it. :)

Obviously, once you've settled on a particular approach, you'd rip out all
the code related to the other approach.

Finally, note that I changed the code so that it uses the DisplayRectangle
rather than the ClipRectangle. The ClipRectangle can potentially change
according to the invalidated area of the control. For example, try your
previous code while dragging the displayed form partially off the bottom
of your screen. You'll find that the text winds up drawing in a different
place depending on where the form last was positioned. Obviously that's
not desirable.

Pete
 
F

Fre

Thank you very much Pete! This bugged me for quite some time and you
helped me reach the wanted result :) Now I can get back to the
business logic of my project ;-)


[...] What
I notice however - and I hoped the example would show this - is that
some characters are sometimes (partly) missing, or sometimes spaces
are hardly visible (or too wide). I do not want exactly the same
result as MS produces, but when spaces are missing the string becomes
less readable and that's not acceptable for me.

Ah, okay. I understand what you're asking about now.

The basic issue is that you're using TextRenderer.MeasureText() to measure
the text, rather than calling Graphics.MeasureString(). Alternatively,
the basic issue is that you're using Graphics.DrawString() to draw the
text, rather than TextRenderer.DrawText().

In other words, you're trying to mix apples and oranges.

Either one is probably okay, but TextRenderer seems more accurate, at
least with my brief test. Using the Graphics methods, you still wind up
with a bit of margin around each drawn string, which throws off the
spacing a little. TextRenderer allows you to specify no padding, and so
it draws multiple strings in the same location (I think) as if it was just
one string.

However, either way, as long as you're consistent about how you're
measuring the text, no characters get cut off or anything like that.

Here's a modified version of your Paint event handler that behaves better:

private void label1_Paint(object sender, PaintEventArgs e)
{
Label labelSender = (Label)sender;
Rectangle rect = labelSender.DisplayRectangle;
Graphics gfx = e.Graphics;
Font font = new Font("Courier New", 10, FontStyle.Regular);
string[] writeThis = { "This drawing ",
"gives",
" me ",
"a",
" serious ",
"headache",
"! Why",
" isn't it ",
"w",
"orkin",
"g?"
};

// text format flags
TextFormatFlags flags = TextFormatFlags.Left;
flags |= TextFormatFlags.NoPadding;
flags |= TextFormatFlags.PreserveGraphicsClipping;
flags |= TextFormatFlags.NoPrefix;
flags |= TextFormatFlags.GlyphOverhangPadding;

// more text layout
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Center;
format.Trimming = StringTrimming.None;
format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
format.FormatFlags |= StringFormatFlags.NoWrap;
format.FormatFlags |= StringFormatFlags.NoFontFallback;

bool fIndigoBackground = false;
Size propsize = new Size(int.MaxValue, int.MaxValue);

rect.Width = 0;

foreach (string strCur in writeThis)
{
//SizeF size = gfx.MeasureString(strCur, font, new
PointF(rect.X, rect.Y), format);
Size size = TextRenderer.MeasureText(gfx, strCur, font,
propsize, flags);

rect.X += rect.Width;
rect.Width = (int)(size.Width + 0.5);

// fill rectangle and draw string (toggle colors)
fIndigoBackground = !fIndigoBackground;
gfx.FillRectangle(fIndigoBackground ? Brushes.Indigo :
Brushes.LightGreen, rect);
//gfx.DrawString(strCur, font, fIndigoBackground ?
Brushes.Wheat : Brushes.Black, rect, format);
TextRenderer.DrawText(gfx, strCur, font, rect,
fIndigoBackground ? Color.Wheat : Color.Black,
fIndigoBackground ? Color.Indigo : Color.LightGreen,
flags);
}
}

You can alternately comment/uncomment the TextRenderer/Graphics lines to
compare the two behavior. Of course, make sure that comment/uncomment in
pairs so that you're always measuring the text the same way you're drawing
it. :)

Obviously, once you've settled on a particular approach, you'd rip out all
the code related to the other approach.

Finally, note that I changed the code so that it uses the DisplayRectangle
rather than the ClipRectangle. The ClipRectangle can potentially change
according to the invalidated area of the control. For example, try your
previous code while dragging the displayed form partially off the bottom
of your screen. You'll find that the text winds up drawing in a different
place depending on where the form last was positioned. Obviously that's
not desirable.

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top