I have an application where I need to dynamically build the content to a Canvas. Everything works just fine, but I am a little unsure of how I can set the y coordinates for the labels in the safest way. For example, I need to add three labels that are essentially lines of text. In Java Swing or C# GDI I would just query the the font for the line height and add that value to the y coordinate of the drawText command.
This is my code.
double y = 0.0;
_line1.Content = "Line1";
_line1.SetValue(Canvas.TopProperty, y);
_line1.SetValue(Canvas.LeftProperty, 0.0);
CanvasChart.Children.Add(_line1);
double textHeight = _line1.Height;
y += textHeight;
_line2.Content = "Line2";
_line2.SetValue(Canvas.TopProperty, 0.0);
_line2.SetValue(Canvas.LeftProperty, y);
CanvasChart.Children.Add(_line2);
This does not work because _line1.Height does not seem to be set to anything useful at this point. I suppose it has not rendered yet. The above code is in the loaded event for the window. ActualHeight does not help either.
Most code that I've seen seems to just set them to a hard coded value. That I suppose looks right on the developer's display, and you just hope looks good at other resolutions/DPI. In Swing and GDI I always had the best results finding out exactly how many pixels a string will be rendered at and using this to offset the next line.
You must call the Measure method, specifying an infinite available size. This will update the DesiredSize of the control:
_line1.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double textHeight = _line1.DesiredSize.Height;
Another easy way to achieve the desired effect is to put the labels in a StackPanel.
In Swing and GDI I always had the best results finding out exactly how many pixels a string will be rendered at and using this to offset the next line.
This is possible in WPF as well. The GlyphTypeface class provides the AdvanceWidths and AdvanceHeights properties for each character in a typeface. By using CharacterToGlyphMap, you can map a character to an index within the AdvanceHeights, and use that to determine the actual height of any character.
For a detailed example, see GlyphRun and So Forth.
Related
I am currently working on a module to create charts to display data.
I use System.Windows.Forms.DataVisualization.Charting.Chart.
I have two striplines showing the the average result we got and another one showing what we want.
So far I was really happy with what I had but I want to add explicit arrow to point these lines. And I can't figure out how to do it.
I saw that Line Annotation might be of help but I couldn't find a way to do what I wanted.
Here is an example of what I would like to do :
You have a choice of using
Annotations or
GDI+ drawing
In both cases the challenge is to get the positions right.
The more natural way to go is using Annotations, so let's look at this first:
There are various types but different capabilities; text can be displayed by RectangleAnnotation or a TextAnnotation. Lines and arrowheads can only be displayed by LineAnnotations. So we need a pair of Line- plus TextAnnotation for each of your two lines.
Like many other chart elements annotations are positioned in percentages of their respective containers; this makes things rather tricky at times.
To place a line annotation all to the right of the chart you could set its X property to 100; to let it go to the left you set the width to a negative number. The problems are starting after that..
To find out where the right edge of the ChartArea is you need to code the Pre- or PostPaint event and use the ToRectangleF method.
To find out the y-value you will want to calculate it from a data value; for this you can use the AxisY.ValueToPixelPosition method, which converts to pixels, from which you can calculate the percantage using the chart's ClientArea along with the ChartArea percentage size.
Complicated? Yup. Annotations get a lot simpler to use if you can anchor them to a certain DataPoint; but yours are outside the ChartArea..
Here is a function that should help when doing the calculations:
double PercentFromValue(Chart chart, ChartArea ca, double value)
{
Axis ay = ca.AxisY;
RectangleF car = ca.Position.ToRectangleF();
double py = ay.ValueToPixelPosition(value);
int caHeight = (int)(chart.ClientRectangle.Height * car.Height / 100f);
return 100d * py / caHeight;
}
Note that it will only work reliably when called from of of the Pre/PostPaint events..
So this is an example of a PrePaint event that positions a LineAnnotation lAnn:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
Rectangle cr = chart1.ClientRectangle;
ChartArea ca = chart1.ChartAreas[0];
RectangleF car = ca.Position.ToRectangleF();
lAnn.Width = car.Width - lAnn.X;
lAnn.Y = PercentFromValue(chart1, ca, someDataValue);
}
When you insert a valid DataPoint YValue the starting y-position will be set. You can play with it until you find a nice combination of setting the four position properties..
When creating and adding the four(!) annotations you may want to keep class level references, so you won't have to refer to them from the Annotations collection..
For the LineAnnotation you will want to set the linewidth, color and the capstyle, either using the EndCap or the StartCap:
lAnn.EndCap = LineAnchorCapStyle.Arrow;
GDI+ drawing is more straight-forward, provided you know where you want to draw the lines and the text.
It is also done in the PrePaint event, again using the ValueToPixelPosition to find the pixelposition of the two data lines.. Other than that is all the usual stuff with Graphics.DrawLine, a Pen with and Start- or EndCap and Graphics.DrawString or maybe TextRenderer.DrawText..
I'm frankly not sure which way I would choose..
create triangle image and set the marker image as:
Chart1.Series.Points.AddXY(0, 10);
Chart1.Series.Points.AddXY(20,10);
Chart1.Series[0].Points[1].MarkerImage = "TriangleImage.bmp";
Chart1.Series[0].Points[1].MarkerImageTransparentColor = Color.White;
or
Chart1.Series[0].Points[1].MarkerStyle = MarkerStyle.Triangle;
I'm working on a Winforms app that contains a large map image (5500px by 2500px). I've set it up so the map starts in full size, but the user can zoom out to a few different scales to see more of the map. The user is able to drag the map around to shift what they are looking at (like Google Maps, Bing Maps, Civilization, etc.).
When the map is full sized (scale = 1.0), I am able to prevent the user from scrolling past the borders of the image. I do this by calculating if they are trying to move past 0, or past the image width - current window size, similar to this:
if (_currHScroll <= 0) {
_currHScroll = 0;
}
This all works just fine. But, when I zoom out on the map (thus, making the image smaller), the limits for the bottom and right of the map break down. I know why this happens--because the Transform that is performed basically "compresses" the map a little bit, and so what used to be a 5000 px image is now smaller, depending on the scale. But, my limiters are based on the image size.
So, the user can scroll past the end of the map, and just sees white space. Worse things happen, I realize, but if possible I'd like to keep them from doing that.
I'm sure there is a straight-forward way to do this, but I haven't figured it out yet. I've tried simply multiplying my calculation by the scale, but that didn't seem to work (seems to under-estimate the size initially, then over-estimate on the smallest sizes). I've tried calculating the transform location of the bottom right of the image, and using that, but it turns out, that number is inverted, and I can't find what it relates to.
I'm including my transform point method here. It works just fine. It tells me, regardless of zoom level, what pixel was clicked on the original image. Thus, if someone clicks on point 200, 200 but the image is scaled at .5, it will show something like 400,400 as what was clicked (but, as I said, I don't think the scale value is a multiplier--using this just for demonstration purposes).
public Point GetTransformedPoint(Point mousePoint) {
Matrix clickTransform = _mapTransform.Clone();
Point[] xPoints = { new Point(mousePoint.X, mousePoint.Y) };
clickTransform.Invert();
clickTransform.TransformPoints(xPoints);
Debug.Print("Orig: {0}, {1} -- Trans: {2}, {3}", mousePoint.X, mousePoint.Y, xPoints[0].X, xPoints[0].Y);
return xPoints[0];
}
Many thanks in advance. I'm sure it's something relatively easy that I'm overlooking, but after several hours, I'm just not finding it.
If i understand right, you can calculate the maximum with your method GetTransformedPoint by using width and height from your Image as Point. The result can then be used inside your check...
And by the way, you are right, the scale value is a multiplier used as a factor. The only thing is, you have to cast the result to an integer.
I'm working a 2d tile-editor, used Winforms and XNA(for the map rendering) and i have a problem with the custom scrollbar.
I would like to know the formula for calculating the map scrolling depending the position value of my scrollbar.
Let me explain
Actually, i have a functional code but is not exact.
Here is my calculation (is the problem):
(Point Map.PixelMapSize = map size in pixel)
hScrollBarMap.Minimum = 0;
hScrollBarMap.Maximum = 100;
hScrollBarMap.LargeChange = hScrollBarMap.Size.Width * 100 /(Map.PixelMapSize.X);
mapScrollCalcul = ((hScrollBarMap.Value - hScrollBarMap.Minimum) * 100 / (hScrollBarMap.Maximum - hScrollBarMap.Minimum)) * Map.PixelMapSize.X / 100;
Sorry, I do not know how to explain. I found it by making a lot of tests...
But, the mapScrollCalcul is the final calcul to be applied to map position display.
What is the exact calculation for this ?
I hope you understand, I'm not speak English, just little understand.
But I understand you. :) (programmation language is universal)
Thank you for reading and, maybe, future responses.
You are overcomplicating things.
First, you are setting your LargeChange value relative to your image, not your view, and your scroll.Maximum relative to... what, exactly?
On MSDN, they say:
User interface guidelines suggest that the SmallChange and LargeChange
properties are set relative to the size of the view that the user
sees, not to the total size including the unseen part. For example, if
you have a picture box with scroll bars displaying a large image, the
SmallChange and LargeChange properties should be set relative to the
size of the picture box, not to the size of the image.
Now, lets assume that your image is 1000x1000 and your view is 100x100, and you are scrolling vertically. Then:
myVscroll.Minimum=0;
myVscroll.Maximum=image.Height; //1000
myVscroll.SmallChange = 10; // here: view.Height/10 or other value that makes scrolling look smooth
myVscroll.LargeChange = 100; // in this example, this is set to view.Height
And finally:
mapScrollCalcul = myVscroll.Value;
System.Windows.Forms.TextRenderer.DrawText method renders formatted text with or without left and right padding depending on the value of the flags parameter:
TextFormatFlags.NoPadding - fits the text tightly into the bounding box,
TextFormatFlags.GlyphOverhangPadding - adds some left and right margins,
TextFormatFlags.LeftAndRightPadding - adds even bigger margins.
Now, my question is how can I get the exact amount of padding (left and right) added by DrawText to the text for a given device context, string, font etc?
I've dug into .NET 4 with .NET Reflector and found that TextRenderer calculates "overhang padding" which is 1/6 of the font's height and then multiplies this value to calculate left and right margins using these coefficients:
left 1.0, right 1.5 for TextFormatFlags.GlyphOverhangPadding,
left 2.0, right 2.5 for TextFormatFlags.LeftAndRightPadding.
The resulting values are rounded up and passed to the DrawTextExA or DrawTextExW native API functions. It's difficult to recreate this process because font's height is taken not from System.Drawing.Font but from System.Windows.Forms.Internal.WindowsFont and these classes return different values for the same font. And a lot of other internal BCL classes from the System.Windows.Forms.Internal namespace are involved. Decompiling all of them and reusing their code in my app is not an option, because that would be a serious .NET implementation dependency. That's why I need to know if there is some public API in WinForms or at least which Windows functions I can use to get the values of left and right margins.
Note: I've tried to TextRenderer.MeasureText with and without padding and compare the results but that gave me only the sum of left and right margins and I need them separately.
Note 2: In case you wonder why I need this: I want to draw one string with multiple fonts/colors. That involves calling DrawText once for every uniformly formatted substring with NoPadding option (so that the text doesn't spread) but I also want to add manually normal GlyphOverhangPadding at the very beginning and very end of the whole multi-format text.
The value you need for computing left and right margins is TEXTMETRIC.tmHeight, which is possible to obtain using Win32 API.
However, I found that tmHeight is just a line height of a font in pixels, so these three approaches will give you the same value (you can use whichever you like in your code):
int apiHeight = GetTextMetrics(graphics, font).tmHeight;
int gdiHeight = TextRenderer.MeasureString(...).Height;
int gdipHeight = (int)Math.Ceiling(font.GetHeight(graphics));
To obtain left and right margins, we use the same code as TextRenderer does under the hood:
private const float ItalicPaddingFactor = 0.5f;
...
float overhangPadding = (gdiHeight / 6.0f);
//NOTE: proper margins for TextFormatFlags.LeftAndRightPadding flag
//int leftMargin = (int)Math.Ceiling(overhangPadding);
//int rightMargin = (int)Math.Ceiling(overhangPadding * (2 + ItalicPaddingFactor));
//NOTE: proper margins for TextFormatFlags.GlyphOverhangPadding flag
int leftMargin = (int)Math.Ceiling(overhangPadding);
int rightMargin = (int)Math.Ceiling(overhangPadding * (1 + ItalicPaddingFactor));
Size sizeOverhangPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.GlyphOverhangPadding);
Size sizeNoPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.NoPadding);
int overallPadding = (sizeOverhangPadding.Width - sizeNoPadding.Width);
Now you can easily check that
(leftMargin + rightMargin) == overallPadding
Just to note:
I needed to solve this problem in order to implement "Search Highlight" feature in a ListView-based control that uses GDI text rendering:
Works like a charm :)
This answer is an excerpt from here - http://www.techyv.com/questions/how-get-exact-text-margins-used-textrenderer#comment-35164
If you have ever wanted a Label or TextBox in Windows Forms that performs a little more like on the web, then you've probably figured out that there's no intuitive way to make a Label or TextBox automatically adjust its height to fit the text it contains. While it may not be intuitive, it's definitely not impossible.
In this example, I'll use a TextBox (you could just as easily use a Label) that is docked to the top of a form.To use this, add aTextBox called MyTextBox to the form, and set Dock to DockStyle.Top. Wire up the Resize event of the TextBox to this event handler.
private void MyTextBox_Resize( object sender, EventArgs e )
{
// Grab a reference to the TextBox
TextBox tb = sender as TextBox;
// Figure out how much space is used for borders, etc.
int chromeHeight = tb.Height - tb.ClientSize.Height;
// Create a proposed size that is very tall, but exact in width.
Size proposedSize = new Size( tb.ClientSize.Width, int.MaxValue );
// Measure the text using the TextRenderer
Size textSize = TextRenderer.MeasureText( tb.Text, tb.Font,
proposedSize, TextFormatFlags.TextBoxControl
| TextFormatFlags.WordBreak );
// Adjust the height to include the text height, chrome height,
// and vertical margin
tb.Height = chromeHeight + textSize.Height
+ tb.Margin.Vertical;
}
If you want to resize the a Label or TextBox that is not docked (for example, one that is in a FlowLayoutPanel or other Panel, or just placed on the form), then you can handle the Form's Resize even instead, and just modify the Control's properties directly.
This might seem (very) crude, but this is the only native implementation I can think of:
DrawText draws to an IDeviceContext, which is implemented by Graphics. Now, we can take advantage of that with the following code:
Bitmap bmp = new Bitmap(....);
Graphics graphic = Graphics.FromImage(bmp);
textRenderer.DrawText(graphic,....);
graphic.Dispose();
With the new Bitmap you can go pixel by pixel and count them by some condition.
Again, this method is very crude and wasteful, but at least it's native....
This is not tested but based on the following sources:
http://msdn.microsoft.com/en-us/library/4ftkekek.aspx
http://msdn.microsoft.com/en-us/library/system.drawing.idevicecontext.aspx
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx
http://www.pcreview.co.uk/forums/graphics-bitmap-t1399954.html
I've done something similar a few years ago, to highlight search results (search pattern appears in bold etc.). My implementation was in DevExpress, so the code might not be relevant. If you think it's of use I can copy it, just need to find that implementation.
In System.Windows.Forms, the class to use would be Graphics. It has a MeasureCharacterRanges() method which accepts a StringFormat (start with GenericTypographic and go from there). It is much more appropriate than TextRenderer for displaying a complete string by chaining parts with different styles, fonts or brushes.
You've gone way further than me with the actual padding measuring. DevExpress's controls gave you the text bounding rectangle to start with so that was done for me.
Here's an article by Pierre Arnaud that came up for me in Google, which touches on this area. Unfortunately the GDI+ "Gory details" link there is broken.
Cheers,
Jonno
The fix is to calculate what MeasureText is going to add:
var formatFlags FormatFlags =
TextFormatFlags.NoPadding |
TextFormatFlags.SingleLine;
int largeWidth = TextRenderer.MeasureText(
" ",
font,
new Size(int.MaxValue, int.MaxValue),
formatFlags
).Width;
int smallWidth = TextRenderer.MeasureText(
" ",
font,
new Size(int.MaxValue, int.MaxValue),
formatFlags
).Width;
int extra = smallWidth - (largeWidth - smallWidth);
We calculate the width of one space and the width of two spaces. Both have the extra width added, so we can extrapolate the extra width that is being added. The added width apparently is always the same, so subtracting extra from every width returned by MeasureText gives the expected results.
I'd like to know if there's a better approach to this problem. I want to resize a label (vertically) to accomodate certain amount of text. My label has a fixed width (about 60 chars wide before it must wrap), about 495 pixels. The font is also a fixed size (12points afaik), but the text is not.
What I want to do is increase the Label Height when there's a "NewLine" or the text must wrap; the idea is that the text is fully visible in the label. The AutoSize doesn't work because it will grow in width, not in height.
Of course I could count the number of NewLines and add: Newlines * LineHeight, and then -given that I manage to put 60 chars per line, just divide the number of chars and add as many LineHeight pixels as needed.
I was wondering if there was a more professional way to do it. Is my approach too "lame" ?
Thanks in advance.
How about Graphics.MeasureString, with the overload that accepts a string, the font, and the max width? This returns a SizeF, so you can round round-off the Height.
using(Graphics g = CreateGraphics()) {
SizeF size = g.MeasureString(text, lbl.Font, 495);
lbl.Height = (int) Math.Ceiling(size.Height);
lbl.Text = text;
}
System.Drawing.Graphics has a MeasureString method that you can use for this purpose. Use the overload that takes a string, a font, and an int "width" parameter; this last parameter specifies the maximum width allowed for the string - use the set width of your label for this parameter.
MeasureString returns a SizeF object. Use the Height property of this returned object to set the height of your label.
Note: to get a Graphics object for this purpose, you can call this.CreateGraphics.
Graphics.MeasureString() will probably help you.
This is also one of the only usecases for using the Control.CreateGraphics() call!
Size maxSize = new Size(495, int.MaxValue);
_label.Height = TextRenderer.MeasureText(_label.Text , _label.Font, maxSize).Height;
This "answer" is for future reference and to combat the initial assumption that AutoSize = true implies that it (a WinForms label) will never grow in height.
The following link shows the various effects of AutoSize = true with other properties such as MaximumSize. Depending upon the expected use of the question it may be appropriate to follow one of these approaches.
http://blogs.msdn.com/jfoscoding/articles/478299.aspx
I would like to propose an alternative since it's a label we are talking about and not just text rendering: GetPreferredSize. I tried Mark's answer and the problem is that it almost works: the label has some "padding" around the text and this needs to be taken into account in the width value of MeasureString. I couldn't find any apparent way to get this padding. While digging, I found this answer where the poster suggests to set the FlatStyle to System. That works, but unfortunately breaks the TextAlign which I wanted it to be MiddleLeft.
I poked into the Label code and I found out the GetPreferredSize which takes into account all the weird settings (FlatStyle, UseCompatibleTextRendering etc) and can give you the correct result for each case. So instead of g.MeasureString(text, lbl.Font, 495); you can do instead
lbl.GetPreferredSize(new Size(495, 0));
or even like this since the label size is already known:
lbl.GetPreferredSize(new Size(lbl.Width, 0));
In case you are wondering, 0 and 1 will be treated as int.MaxValue.
I don't know when GetPreferredSize was introduced, so back in 2008 when Mark wrote his answer, the above might not have been relevant. But if you still need something similar in 2021, GetPreferredSize might be a tiny bit handier -which returns a Size and not a SizeF.
I posted a user control which solves this problem in the accepted answer here:
Autoscale Font in a TextBox Control so that its as big as possible and still fits in text area bounds
The control extends RichTextBox. It has a method: ScaleFontToFit that will automatically resize the font to make all the text fit.
Neat thing is it respects the multiline property. If it's true it allows words to wrap, Otherwise it doesn't.
Well the 60 chars might be valid for your test text, but not all characters have the same width. For instance, compare
llllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
and
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
They both have 60 characters, and yet have vastly differing widths.
Is there any downside to using the TextRenderer class to measure the string (like in Marc's response) instead of going through the work to create a Graphics object, etc?
According to this article you should use TextRenderer if you are going to use a Windows Form control for the final output. TextRenderer and Graphics.MeasureString will give different results, so use the one that matches your final mode of output.
In some cases where you must use compact framework, which does not have any override methods for MeasureString(), you might consider calculating the height by yourself.
private int YukseklikAyarla(string p, Font font, int maxWidth)
{
int iHeight = 0;
using (Graphics g = CreateGraphics())
{
var sizes = g.MeasureString(p, font); // THE ONLY METHOD WE ARE ALLOWED TO USE
iHeight = (int)Math.Round(sizes.Height);
var multiplier = (int)Math.Round((double)sizes.Width) / maxWidth; // DIVIDING THE TEXT WIDTH TO SEE HOW MANY LINES IT CAN HAS
if (multiplier > 0)
{
iHeight = (int)(iHeight * (multiplier + 1)); // WE ADD 1 HERE BECAUSE THE TEXT ALREADY HAS A LINE
}
}
return iHeight;
}
Although, its an old thread, thought it might help new visitors.
In C#, you could use control.width