Auto font size for TextBlock? - wpf

I have a XAML form with a TextBlock object that is set to a width of 500 and a height of 150 (for example).
I'd like to figure out a way to have the font size change automatcally depending on the text assigned to the object, such that it's as big as possible without overflowing the assigned bounds. Including word wrap as necessary and possible so that the text fills the available space both horizontally and vertically.
In other words, if the text is "Star" it would use a font size of 40 but for "superstar" it might get set to 18.45, using smaller text so the entire width of the object gets used. (The font's normal aspect ratio must be maintained.)
My experimenting with using a ViewBox hasn't provided the desired results as yet.

Related

Is there any difference between WPF TextBlock and TextBox?

What criteria must I consider when selecting one of these two controls?
Common to both TextBlocks and TextBoxes:
Can be used to display text
Can be set to specific Height and Width or be set to Auto so that they grow in size with the text.
Can set font size, font type, font styling, to wrap and to range left, right or centred.
Can have opacity set and have Pixel Shaders applied.
TextBlock:
Used for displaying text more focused typographically.
Can contain text set to different colors, fonts and sizes.
The line height can also be increased from the default setting to give more space between each line of text.
Text inside a TextBlock cannot be made selectable by the user.
TextBox:
Used for displaying text more focused for content input or when content is needed to be made selectable by the user.
Can only be set to one colour, one font size, one font type etc.
Have fixed Line Spacing.
Can also be set to a fixed height and width but also have scrollbars switched on to allow content to expand.
TextBlock is more lightweight control for displaying text and TextBox is used when you require user input or edit existing text. Proof for mem usage.

How do I calculate the required size for a CheckedListBox?

I have written some code to automatically scale a CheckedListBox to its contents using mListBox.ItemHeight and mListBox.CreateGraphics().MeasureString(...).
The output from the string measurements is a bit dubious but what really puzzles me is how much to add for borders and such.
I tried both SystemInformation.Border3DSize (= 2) as well as the difference between ClientSize and Size (= 4).
But taking mListBox.ItemHeight * mListBox.Items.Count + 4 for the height makes it one pixel too small and a scrollbar appears.
For the width it does not work at all because it does not take the size of the checkboxes into account for which I can't seem to find a source.
How should I determine the size of the control?
In cases like this, it is typically easier to set the ClientSize rather than the whole Size. One thing to note about ItemHeight is that it does not include the margins of the item. Using a CheckedListBox with default settings, I had an ItemHeight of 13. But the ItemRectangle property had a height of 15.
So here is what I did. I added 9 items to the CheckedListBox (the first Item was longer than the rest), keeping the default size of the control as set by the designer. Then in the constructor of the form, I set the ClientSize like so:
this.checkedListBox1.ClientSize = new Size(TextRenderer.MeasureText(checkedListBox1.Items[0].ToString(), checkedListBox1.Font).Width + 20, checkedListBox1.GetItemRectangle(0).Height * checkedListBox1.Items.Count);
Notice I used TextRenderer.MeasureText to measure the text. It will typically give you better values than Graphics.MeasureString. By default, TextRenderer included a bit of padding in it's measurement. I also included a 20 pixel padding to account for the checkbox. Anyway, with TextRenderer.MeasureText, and the 20 pixel padding for width, and ItemRectangle * Items.Count for the height, that gave me a CheckedListBox that was sized to its contents without ScrollBars.
EDIT: If item widths vary, it may complicate setting the width, as you'll want to set the width based on the longest item. In this case you'll need to measure each item and keep track of which one was the longest.
EDIT 2: Ok so I dug around in the CheckedListBox source code. They use a default size of 13 plus a 3 pixel padding for the width and height of the checkbox. But, if VisualStyles is enabled, they call CheckBoxRenderer.GetGlyphSize to get the size to use because it takes into account the DPI settings. Most machines are set at 96 DPI so that method will still return a size of (13,13). So when you're measuring the text of the item, you can also pass the Graphics object and a CheckBoxState enum to the CheckBoxRenderer.GetGlyphSize to get a size. I used System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal for the state, but I'm not sure that the state matters. I tried CheckBoxState.MixedDisabled as well and it returned the same size, (13,13).
So to summarize, you can use CheckBoxRenderer to get the size of the checkbox, but you will still probably need to use some padding. This reduces the need for hardcoding a magic number to account for the checkbox. Since the checkbox is drawn and isn't an actual control, its size can't be determined like sizes of controls can.
Here is a link to the source of CheckedListBox. It wouldn't hurt to look at it. Specifically, look at the protected override void OnDrawItem method.
CheckedListBox Source

How to set the PREFERRED size of a WPF control

Is there a way to set the preferred size of a WPF control without setting its maximum size?
I normally set all of my WPF controls to have have automatic height and width, and I set the alignment to stretch. That works great in a lot of cases. My windows come up in a reasonable size by default. If the user stretches the window, the extra space will go where I think it should.
This broke when I set the width of a DataGridTextColumn to "*". Now suddenly the preferred size for my table is several times bigger than my screen! The initial size for my window is exactly the size of my screen.
Of course, I could set the MaxWidth of my table (or my column), but I don't want to do that. The max width was fine at infinity.
I tried changing the Width property to a fixed number of pixels. That seems to change the maximum width. Even though I set HorizontalContentAlignment="Stretch" in the table, the table is centered and the width does not change.
Is there a way around this? I like auto-sizes most of the time. And I like setting the width of a column to "*". Is there a way to make them work together?
It seems like there's a property somewhere that I can't find. At some point the container has to ask my table what size it wants to be. That answer might be ignored or adjusted, but the table had to answer that question. It seems like I should be able to change that value, without changing the max size. (And it seems like if I say "stretch" and there is extra space, my table should be stretched.)
Thank you.

Determining size of a control before rendering process

I've got a composite control consisting of a rectangle and a few dynamically created labels (dynamically, because during design time I don't know how many labels shall be displayed). The labels' positions (margins) are evaluated during run time by combining whole control size, additional collection passed through dependency property and by heights of labels themselves.
Unfortunately, I didn't came into deterministic way of determining label height before one was rendered. ActualWidth and ActualHeight are 0 before the labels are displayed, Width/Height is not set, because I wish the labels to size themselves basing on their contents, DesiredSize returns either 0, correct size or size exceeding the real label size (like 2 or 3 times), RenderSize returns either valid size or 0 and it is like first label returns valid size and second one - 0.0, without any noticeable reason.
I've tried calling Measure() on the labels with double.PositiveInfinity passed by only to reach situation, when DesiredSize was way bigger than expected (all labels have the same font and consist only of numbers, so they all shall have more less similar size, but first had ~16 pixels, second - ~36, though after the rendering, RenderSize was valid for both of them).
Is there a deterministic way to check desired control size, based only on its contents (not on alignment/margins) before it is rendered on screen?
You can use UpdateLayout to force a measure / layout pass. After invoking this method ActualWidth and ActualHeight will have the correct values.

Resizing Labels

I have a chart in WPF with a lot of labels. The text on these labels is dynamically loaded and subject to change. If I set the width just to auto, then these labels may overlap, which makes the text unreadable.
The chart support multiple sizes, so if it gets larger, then the bars are re sized and there is more space for text. Now I want to adjust the text to the space which is available. If it gets too small, I don't want to display the label anymore (a tooltip is available, so the user still gets the required information). Consider the string "Case 1, blah blah", there is probably not enough space to display the whole string, but just the first word. In this case I want the string to be "Case 1..", with .. indicating that there is some more information in the tooltip.
I can determine the length available for the string. But how can I determine the space a single letter will take? Of course I could also just re size the label, but then it would just cut off the string anywhere which is probably not helpful for the user (and looks ugly).
Any ideas?
If you can use TextBlocks instead of labels then they have a TextTrimming property which will do this for you to either the nearest character or the nearest word.
While you seem happy with the TextTrimming property, I'll edit this to add that the TextBox control has a GetRectFromCharacterIndex method that would allow you to find out the size on screen of one or more characters as long as the font settings matched your label. This might be useful if you wanted to trim at specific places in the label rather than the nearest character / word.
Not an expert in WPF, but I would think that you'll need to do this in code rather than XAML.
Start by obtaining the actual pixel width of the space available for the text.
Then look at the character set, dot pitch etc. utilised on the XAML front end and from there calculate the pixel width required per character.
You could also look at changing the character sizes as well as reducing the label length.

Resources