|
I have a label control. inside that label control i drwn a rectangle. inside this rectangle i wrote a string (eg : " i am the string ") using drawstring.
Now i would like to highlight the text area (" i am the string ") with some color. Do u have any idea ?
Sreejith Nair
[ My Articles ]
|
|
|
|
|
Hi,
I noticed that both of functions mentioned in this article didn't worked for me. Well, I was using a non installed font and getting wrong height of the output image. The same is with installed font though, but that is less.
Here is the simple code I used.
bmpCanvas = New Bitmap(1, 1)
Dim gr As Graphics = Graphics.FromImage(bmpCanvas)
Dim textSize As SizeF
textSize = gr.MeasureString(Text, CreateFont, Int32.MaxValue, StringFormat.GenericTypographic)
Though I was not using last two parameters of MeasureString but after reading one post from the same article, I tried but of no use. Its returning wrong height (less than the actual height). Here is the CreateFont method.
Dim mFont As Font
Dim mFontStyle As FontStyle
If Me.FontBold Then mFontStyle += FontStyle.Bold
If Me.FontItalic Then mFontStyle += FontStyle.Italic
If Me.FontStrikeThrough Then mFontStyle += FontStyle.Strikeout
If Me.FontUnderLined Then mFontStyle += FontStyle.Underline
If Me.UsePrivateFont Then
If Me.PrivateFontPath = "" Then Throw New Exception("Private Font File is missing")
Dim mFontCol As New PrivateFontCollection
mFontCol.AddFontFile(Me.PrivateFontPath)
Try
mFont = New Font(mFontCol.Families(0), Me.FontSize, mFontStyle)
Catch ex As Exception
'Failed to load private font, use standard instead of private
Me.UsePrivateFont = False
Me.FontName = "Arial"
Return CreateFont(True)
End Try
Else
mFont = New Font(Me.FontName, Me.FontSize, mFontStyle, GraphicsUnit.Pixel)
End If
Return mFont
Any idea if I am doing something wron ?
thanks in advance.
Sameers (theAngrycodeR)
theangrycoder@yahoo.com
http://www.theangrycoder.com
|
|
|
|
|
I dont see why you need to add 1 there. I found that a bounding rectangle of width 501 pixels just enough to print 67 characters with courier 9 has a resulting rectangle of Left:2,Width:499
btw, this method doesn't measure string width right all the time, I mean, using this method on a 62-char string and a 10-char string (both with same monospace font) doesn't give the same char width...
|
|
|
|
|
if i want to use your function in class.
how i can do it? give me the example plz.
thanks
|
|
|
|
|
Just copy and paste one of the static MeasureDisplayStringWidth methods published on this page; if you class is called X, then simply use X.MeasureDisplayStringWidth(graphics, "text", font) to compute the real width. There is really nothing special about it...
|
|
|
|
|
I have played with your function a little bit and I think, that the return value should be:
return Convert.ToInt32 (Math.Round (rect.Right - rect.Left));
Sometimes rect.Left seems to be > 0 for I get better results like this.
Regards,
Felix Arends
|
|
|
|
|
Indeed, depending on the left-side and right-side bearing, your function will return the real width occupied in pixels, not just the advance as mine did... Depends on what you want to use the pseudo-MeasureString for.
|
|
|
|
|
How about (VB.NET):
g.MeasureString(Text, Font, Int32.MaxValue, StringFormat.GenericTypographic)
This is the SIMPLEST Way
Or isn't it ???
|
|
|
|
|
Yeah, this is surely the simplest way of doing it, but I don't get the real length in all cases... That's why I propose this alternative solution.
But consider also the other thread "A better way..." which seems to have been happy with your method too.
|
|
|
|
|
Also check the result with or without using "StringFormat.GenericTypographic" for a string like "12:34" and "12 34"(eg. time):
without: the width and the character-spacing of both strings are the same
with: the width and the character-spacing of both strings are different
IMHO: There are some other mystique behaviours.
Hint/Information: A nice book comes from Wrox: "GDI+ - Creating Custom Controls with C#"
|
|
|
|
|
Ohh, sorry
i forgot the author, the previous answer comes from me.
|
|
|
|
|
You have not understod the problem at all, let me enlighten you :->
Loop over string consisting a number of lower-L's , like "l", "ll", "lll", "llll"
In Tahoma 8, looking at the screen the drawn texts are getting evenly longer by exactly and allways two pixels.
Well, MeasureString (even with GenericTypographic) gives you some floats, some times they are close to 2 sometimes they are close to 3, ist just not useful for pixel-wise use cases, for example, one is not able to paint grid below the string that would fit each character.
|
|
|
|
|
The SetMeasurableCharacterRanges/MeasureCharacterRanges was exactly what I needed.
Thx
|
|
|
|
|
Yeah! Me too - I've been looking for a method like this for days!
Why didn't I just look at the code project at the first place!!??
Great job!
|
|
|
|
|
if you use non integer font size like 8.25 (default for label) measure string does not work correctly.
|
|
|
|
|
Please make sure that the version you are using has the correct test in HelperTools.MeasureDisplayString . If it reads:
if (bitmap.GetPixel (i, 0).R == 0)
replace it with
if (bitmap.GetPixel (i, 0).R != 255)
and it should work. I'm sorry for the inconvenience. The first version of this library had this problem.
|
|
|
|
|
I found this technique through a GDI+ KB article (Q307208) at http://support.microsoft.com/default.aspx?scid=kb;EN-US;q307208
Summary: to measure a string without resolution dependent effects and 1/6 em gaps you must use a GenericTypographic StringFormat.
Something like:
sfmt = StringFormat.GenericTypographic;
size = graphics.MeasureString( m_Text, m_ActiveFont, 1000, sfmt );
Although the article doesn't say it, I believe the GenericTypographic StringFormat sets some internal bits that are not available through the API any other way, at least I haven't found them (and I've been trying).
|
|
|
|
|
That's correct. I used StringFormat.GenericTypographic in a project and it gave the correct measurements.
|
|
|
|
|
Great!! Useful
|
|
|
|
|
your code seems to me little bit not-standard (but if there is no another solution... - see your's excelent link (bottom))
- (working properly) (with non-proportional fonts) it will "show more" everytime
- what about for()-change measured_width at final "found" only? (what when | sign will have no bottom/center or another choosed pixel? or what when let say antialiasing with display it in not-black (gray)?)
- why you are not using original text only? (with full-line-height search and final to-right offset of course)
t!
|
|
|
|
|
This code could indeed be improved by (1) making sure that the '|' character has a pixel at the [y] position we are searching (all fonts I checked were OK with respect to this problem), (2) checking against != 255 rather than == 0 (if font is anti-aliased).
I chose not to use full-line-height search in order to improve speed.
|
|
|
|
|
Dear Pierre,
Thank you for suggesting the MeasureCharacterRanges() method as a technique to provide an accurate report of a string's length and height. I am very grateful to you.
I originally observed that I still found small errors from this method, however on closer examination it appears that:
1) I needed to set FormatFlags to MeasureTrailingSpaces; and
2) this function returns a rectangle of floating point numbers. when working in pixels, the Rectangle.Round() method I was using in my calculations introduced an accumulating error.
MeasurableCharacterRanges() is therefore the preferred solution to the MeasureString problem, however I have included the relevant code for anyone who wants to wrap GDI calls to achieve the same ends (using DrawText()):
Here are the C# GDI declarations (I wrapped these into a class called gdiapi)
[DllImport("gdi32.dll")]
public static extern int CreateFont(
int nHeight,
int nWidth,
int nEscapement,
int nOrientation,
int nWeight,
uint bItalic,
uint bUnderline,
uint cStrikeOut,
uint nCharSet,
uint nOutPrecision,
uint nClipPrecision,
uint nQuality,
uint nPitchAndFamily,
String lpszFacename );
The rectangle structure for DrawText():
[StructLayout(LayoutKind.Sequential)]
public struct CRECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern int DrawText(
int hDc,
String lpStr,
int nCount,
ref CRECT lpRect,
int wFormat);
Here is the declaration for SelectObject():
[DllImport("gdi32.dll")]
public static extern int SelectObject(int hDc, int hObject);
And so I could draw a line around my text:
[DllImport("gdi32.dll")]
public static extern int LineTo(int hDc, int x, int y);
For my example, I created a simple form with a PictureBox control called 'pcbOne'. I then created a Bitmap object and attached it to the Image property of 'pcbOne' as shown below:
Bitmap aBmp = new Bitmap(600, 400, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
pcbOne.Image = aBmp;
(you probably want to fill the bitmap with a background color at this point)
To use:
// We need the Device Context first
Graphics eg = Graphics.FromImage(pcbOne.Image);
int iHdc = (int)eg.GetHdc();
// must create a font because the hdc from GetHdc() has no font object attached!
// for no apparent reason, I now select maroon for the text color
SetTextColor(iHdc, 343434);
// you can get font height by creating a Font object and calling Font.ToLogFont() to
// populate a LOGFONT object (code ommited because it's two am and I'm tired). I just
// happen to know that -21 corresponds to a font size of 15.75F
int hfFont = gdiapi.CreateFont(-21,0,0,0,0,0,0,0,0,0,0,0,0, "Times New Roman");
// must select the font into the Device Context
int iOldObj = gdiapi.SelectObject(iHdc, hfFont);
// initialize a rectangle object (there is an api to do this too!)
gdiapi.CRECT aRect;
aRect.left = 0;
aRect.top = 0;
aRect.right = 0;
aRect.bottom = 0;
// et voila! now we can size strings and draw them (note that 1024 is DT_CALCRECT)
String aStr = "Calculate the Test size of equation 12 * 4.56 + 12e";
// first measure the rectangle needed to display the text
int i = gdiapi.DrawText(iHdc, aStr, aStr.Length, ref aRect, 1024);
// rectangle now contains the appropriate width and height (ie. right and bottom)
// so we can now draw our text
i = gdiapi.DrawText(iHdc, aStr, aStr.Length, ref aRect, 0);
// and a nice box to show the fit is good
gdiapi.LineTo(iHdc, 0, 0);
gdiapi.LineTo(iHdc, 0, aRect.bottom);
gdiapi.LineTo(iHdc, aRect.right, aRect.bottom);
gdiapi.LineTo(iHdc, aRect.right, 0);
gdiapi.LineTo(iHdc, 0, 0);
// what you should now have is a maroon line of text with a rectangle drawn around it
// in my actual code I only create the font object once (in the constructor for a
// gdiapi object) but I do (un)SelectObject() the font after each use (code not here)
// release resources
eg.ReleaseHdc((System.IntPtr)iHdc);
eg.Dispose();
pcbOne.Refresh();
I apologise if I've accidentally included a typo here or there. I am left wondering if Microsoft deliberately sabotaged the MeasureString API. I've found several other APIs which don't quite work 'as advertised' and they all seem to be APIs that SHOULD work first time around. I'd love to know when the Evil Empire plans on fixing these problems!
I know the best solution is to try and stay within GDI+, but at least this code also serves to illustrate the implimentation of calls to GDI and USR DLLs, as well as providing a more-or-less perfect-in-all situations attempt to measure strings.
Regards, and thanks again, Alastair Stell
Only change is constant
|
|
|
|
|