On CreateGraphics().MeasureString

D

Dom

For various reasons, I've had to create my own control that allows me
to display text. The control will highlight a word by placing it in a
gray rectangle as the mouse flies over it. To do this, I split the
text into an array of words, and each word carries it's rectangle,
specifying the x, y, width, and height of the word. Each word is
composed of a string of "char-types" -- numeric, alpha, whitespace
(space and tab), or special (comma, semicolon, etc). For example, in
"Doe, John123" there are 5 words:

1. Doe
2. <comma>
3. <whitespace>
4. John
5. 123

The rectangle for each word is obtained through
CreateGraphics().MeasureString which gets passed four arguments:

1. The text
2. A Font object
3. A StringFormat object
4. The Client width.

The StringFormat object is created by "new StringFormat()". I've
tried various flags, but they don't fix the problem.

The problem is that each word has a noticable white space trailing
it. Sometimes it is large, sometimes small, but always there. This
makes the text look strange, especially with a space coming before the
commas.

Any advice?
 
D

Dom

Don't use "CreateGraphics().MeasureString()".  You need to dispose of
the Graphics instance when you're done with it, which you can't do if
you don't put the reference in a variable somewhere.



What "various flags" did you try?  How did you set them?



Yes.  Post your actual code.

Pete- Hide quoted text -

- Show quoted text -

Thanks for looking into this, Peter. Concerning the flags, I just
used one after the other in the call to StringFormat(). I never tried
to combine the flags.

Here is the code. I've inserted some comments. Something I've just
discovered: The effect depends on the font. The Calibri font works
as it should, but the times new roman font has a whole extra space at
the end of each word.

Begin ----------------------------------------------

private void Sketch(ArrayList Words)
{
int Row = 0;
StringFormat f = new StringFormat();
SizeF s;
PointF p = new Point(m_LeftMargin, m_TopMargin); // This
point is the upper left point for the start of the string.
int ClientWidth = base.Width - m_RightMargin -
m_LeftMargin;

// For each "Word" object, I need the rect to highlight it
foreach (Word w in Words)
{
s = CreateGraphics().MeasureString(w.Text, TextFont,
ClientWidth, f);

// Come here if I need to "wrap around"
if ((p.X + s.Width) > ClientWidth)
{
Row++;
p.Y += TextFont.Height + m_TagGutter +
TagFont.Height + m_LineGutter;
p.X = m_LeftMargin;
}
w.Row = Row;
w.Printable = true; // ((w.Text[0] != ' ' &&
w.Text[0] != '\t') || (p.X != m_LeftMargin)); // White space at the
start of a line is not printable
w.Rect = new RectangleF(p, s);
if (w.Printable) p.X += s.Width;
}
}

End --------------------------------
 
J

Jeff Johnson

The rectangle for each word is obtained through
CreateGraphics().MeasureString which gets passed four arguments:
The problem is that each word has a noticable white space trailing
it. Sometimes it is large, sometimes small, but always there. This
makes the text look strange, especially with a space coming before the
commas.

I seem to recall reading that MeasureString() is simply not reliable. I
believe it has to do with GDI+ itself. But it's been quite a while since
I've seen any discussion on the issue, so I could be remembering things
wrong.
 
D

Dom

I seem to recall reading that MeasureString() is simply not reliable. I
believe it has to do with GDI+ itself. But it's been quite a while since
I've seen any discussion on the issue, so I could be remembering things
wrong.

I now have the answer to this question, and I want to complete this
post in case others have a similar problem.

First, CreateGraphics().MeasureString, and TextRenderer.MeasureText
don't do a good job of measuring a string. After some testing, it
seems like it pads the ending, and spaces are not measured accurately.

Also, CreateGraphics().DrawString does a poor job, since it does not
draw the string at the requested X and Y. It seems to start drawing
at X+?.

This site does a good job of explaining why: http://windowsclient.net/articles/gdiptext.aspx

This site gives two work arounds: http://www.codeproject.com/KB/GDI-plus/measurestring.aspx

But the two work arounds still did not give me the precision I
needed. The solution is really remarkably simple. Bypass the
graphics object in CSharp altogether. Go directly to the Windows
API. You need GetTextExtentPoint32, TextOut, and several related
API's, such as BeginPaint, EndPaint, GetDC, etc.

This will give you absolute precision. I can now get the exact letter
that the mouse is hovering over. I can block a string of text at
will, and I can even insert a carat at the right place when the mouse
is clicked. IT doesn't matter what font is used. It is always
accurate. Nothing else is needed.
 
D

Dom

I now have the answer to this question, and I want to complete this
post in case others have a similar problem.

First, CreateGraphics().MeasureString, and TextRenderer.MeasureText
don't do a good job of measuring a string.  After some testing, it
seems like it pads the ending, and spaces are not measured accurately.

Also, CreateGraphics().DrawString does a poor job, since it does not
draw the string at the requested X and Y.  It seems to start drawing
at X+?.

This site does a good job of explaining why:  http://windowsclient.net/articles/gdiptext.aspx

This site gives two work arounds:  http://www.codeproject.com/KB/GDI-plus/measurestring.aspx

But the two work arounds still did not give me the precision I
needed.  The solution is really remarkably simple.  Bypass the
graphics object in CSharp altogether.  Go directly to the Windows
API.  You need GetTextExtentPoint32, TextOut, and several related
API's, such as BeginPaint, EndPaint, GetDC, etc.

This will give you absolute precision.  I can now get the exact letter
that the mouse is hovering over.  I can block a string of text at
will, and I can even insert a carat at the right place when the  mouse
is clicked.  IT doesn't matter what font is used.  It is always
accurate.  Nothing else is needed.

Back again. The program with the calls to the API had a nasty memory
leak, and I discovered the hard way that it was happening in
Font.ToHfont(). Apparently, this does more than just return a cached
variable (I should have guessed since it's a method, not a property).
To confirm this I searched the web and found:

"GDI memory leak in Windows Forms"

http://megakemp.wordpress.com/2009/02/25/gdi-memory-leak-in-windows-forms/

So, do something like cache the HFont in the constructor.
 

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