How can I have a WPF text block that has the same size for all font weights?

I'll move on to the pursuit: is there a way to tell WPF TextBlock to measure itself so that its size does not change when FontWeight changes?

I have a TextBlock that changes the weight of fonts dynamically based on style. TextBlock is inside the RadioButton , so it is shown in bold if set, Normal otherwise:

 <Style x:Key="BoldWhenChecked" TargetType="RadioButton"> <Style.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter Property="TextElement.FontWeight" Value="Bold" /> </Trigger> </Style.Triggers> </Style 

and here are the switches themselves:

 <StackPanel Orientation="Horizontal"> <RadioButton Style="{StaticResource BoldWhenChecked}"> <TextBlock Text="Item 1" /> </RadioButton> <RadioButton Style="{StaticResource BoldWhenChecked}"> <TextBlock Text="Item 2" /> </RadioButton> <RadioButton Style="{StaticResource BoldWhenChecked}"> <TextBlock Text="Item 3" /> </RadioButton> etc... </StackPanel> 

Unfortunately, since I do not use a fixed-width font, the width of the TextBlock font changes as the font weight changes, and the entire switch bar moves accordingly, which visually shakes.

+6
source share
3 answers

I created a workaround for this by adding a hidden TextBlock to the contents of the RadioButton with its FontStyle explicitly set to Bold :

 <RadioButton Style="{StaticResource BoldWhenChecked}"> <Grid> <TextBlock Text="Item 1" /> <TextBlock Text="Item 1" FontStyle="Bold" Visibility="Hidden" /> </Grid> </RadioButton> 

Thus, when RadioButton is selected and the visible TextBlock in bold, the width does not change, because the hidden TextBlock has already configured the grid correctly.

+4
source

Personally, I would try to arrange my layouts better so that they do not depend on the size of the switches. However, if you absolutely insist on doing it this way, you will need to set the width of each TextBlock according to its size when Bold, and in such a way that it will update it if you need to change the text and / or font family, etc. To do this, you need to bind the Width property to a converter that takes all these other values.

Start with a style that sets the width of the text box:

 <Style TargetType="{x:Type TextBlock}"> <Setter Property="Width"> <Setter.Value> <MultiBinding Converter="{StaticResource TextToWidthConverter}"> <Binding Path="Text" RelativeSource="{RelativeSource Self}" UpdateSourceTrigger="PropertyChanged"/> <Binding Path="FontFamily" RelativeSource="{RelativeSource Self}" UpdateSourceTrigger="PropertyChanged"/> <Binding Path="FontStyle" RelativeSource="{RelativeSource Self}" UpdateSourceTrigger="PropertyChanged"/> <Binding Path="FontStretch" RelativeSource="{RelativeSource Self}" UpdateSourceTrigger="PropertyChanged"/> <Binding Path="FontSize" RelativeSource="{RelativeSource Self}" UpdateSourceTrigger="PropertyChanged"/> </MultiBinding> </Setter.Value> </Setter> </Style> 

Now add the code for the value converter itself (note that I'm using fooobar.com/questions/100732 / ... to measure text):

 public class TextToWidthConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var text = values[0] as String; var fontFamily = values[1] as FontFamily; var fontStyle = (FontStyle)values[2]; var fontStretch = (FontStretch)values[3]; var fontSize = (Double)values[4]; var size = MeasureText(text, fontFamily, fontStyle, FontWeights.Bold, fontStretch, fontSize); return size.Width; } public object[] ConvertBack(object values, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } /// <summary> /// Get the required height and width of the specified text. Uses Glyph's /// </summary> public static Size MeasureText(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize) { Typeface typeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch); GlyphTypeface glyphTypeface; if (!typeface.TryGetGlyphTypeface(out glyphTypeface)) { return MeasureTextSize(text, fontFamily, fontStyle, fontWeight, fontStretch, fontSize); } double totalWidth = 0; double height = 0; for (int n = 0; n < text.Length; n++) { ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[n]]; double width = glyphTypeface.AdvanceWidths[glyphIndex] * fontSize; double glyphHeight = glyphTypeface.AdvanceHeights[glyphIndex] * fontSize; if (glyphHeight > height) { height = glyphHeight; } totalWidth += width; } return new Size(totalWidth, height); } /// <summary> /// Get the required height and width of the specified text. Uses FortammedText /// </summary> public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize) { FormattedText ft = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(fontFamily, fontStyle, fontWeight, fontStretch), fontSize, Brushes.Black); return new Size(ft.Width, ft.Height); } } 

But then again, in a real application, I will try to solve it correctly by putting them in a grid or something like that.

0
source

From your comment here :

"... putting them in a net or something like that." How will it work? That was exactly what I was looking for, but did not know how to do it.

I know this is an old question. And while you are not really talking about this in the question, and the question does not include a good minimal, complete, tested example that fully illustrates your scenario, I suspect that you have (or rather have) some considerations when a group of radio buttons does not change size as the font style changes, but somehow changes it dynamically. For this purpose, I think your work with hidden TextBlock not bad.

But I would say that if you can control the layout of the entire group of radio buttons from outside, then using one of the grid elements to organize the switches themselves will be better, as Mark hinted in his answer.

The key is that grid objects can evenly distribute widths and heights across their columns and rows, and therefore can place radio buttons regardless of their nominal desired size.

For instance:

 <Window x:Class="TestSO20556328BoldRadioButtons.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <p:Style x:Key="BoldWhenChecked" TargetType="RadioButton"> <p:Style.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter Property="TextElement.FontWeight" Value="Bold" /> </Trigger> </p:Style.Triggers> </p:Style> </Window.Resources> <StackPanel> <UniformGrid Columns="3"> <RadioButton Style="{StaticResource BoldWhenChecked}"> <TextBlock Text="Item 1" /> </RadioButton> <RadioButton Style="{StaticResource BoldWhenChecked}"> <TextBlock Text="Item 2" /> </RadioButton> <RadioButton Style="{StaticResource BoldWhenChecked}"> <TextBlock Text="Item 3" /> </RadioButton> </UniformGrid> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <RadioButton Style="{StaticResource BoldWhenChecked}"> <TextBlock Text="Item 1" /> </RadioButton> <RadioButton Style="{StaticResource BoldWhenChecked}" Grid.Column="1"> <TextBlock Text="Item 2" /> </RadioButton> <RadioButton Style="{StaticResource BoldWhenChecked}" Grid.Column="2"> <TextBlock Text="Item 3" /> </RadioButton> </Grid> </StackPanel> </Window> 

Just to show a few different ways to solve the problem.

Please note that the above may work, even if you are otherwise trying to somehow change the group of radio buttons. But without special details in the question, I would not be able to say which specific approach will achieve this goal.

0
source

All Articles