如何创建一个 WPF TextBlock,使不同字重的文本具有相同的大小?

6

我直入主题:有没有办法告诉WPF TextBlock在其FontWeight更改时不改变大小,以便自我测量?

我有一个TextBlock,根据样式动态更改字体重量。 TextBlock位于RadioButton内部,因此在选中时为粗体,在未选中时为正常:

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

以下是单选按钮本身:

<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>

很遗憾,由于我没有使用定宽字体,所以当字体粗细发生变化时,TextBlock 的宽度也会改变,整个单选按钮面板也会相应地移动,这样会显得很突兀。

你能展示一下 RadioButtons 的 XAML 吗? - clcto
你是在为RadioButtons使用控件模板吗? - Fuzhou Hu
3个回答

6

我创建了一个解决方法,通过在RadioButton的内容中添加一个隐藏的TextBlock,并将其FontStyle显式设置为Bold

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

这样当选中RadioButton并使可见的TextBlock变粗时,宽度不会改变,因为隐藏的TextBlock已经适当地调整了网格的大小。


0

来自 你的评论:

"......通过将它们放在网格中或其他方式中。" 这该怎么做?那正是我正在寻找的,但我还没有想出一种方法来实现它。

我知道这是一个老问题。虽然您在问题中没有明确说明,也没有包含一个很好的Minimal, Complete, Verifiable example以完全阐述您的情况,但我怀疑您可能有(或曾经有)某种需求,其中单选按钮组在字体样式更改时不会改变大小,但会以其他方式动态调整大小。为此,我认为您使用隐藏的 TextBlock 的解决方法还不错。

但是,如果您可以在外部控制整个单选按钮组的布局,则使用其中一个网格元素来排列单选按钮本身会更好,如Mark在他的答案中所暗示的那样。

关键是网格对象可以在其列和行上均匀地分配宽度和高度,因此可以独立于其名义期望大小布局单选按钮。

例如:

<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>

只是展示了几种不同的解决问题的方法。

请注意,即使您正在以某种方式动态调整单选按钮组的大小,上述方法也可以工作。但是,如果问题没有更多具体细节,我就无法说出哪种具体方法可以实现该目标。


0

个人建议更好地组织布局,使其不依赖于单选按钮的大小。尽管如此,如果您坚持要这样做,那么您需要根据 TextBlock 加粗时的大小设置每个 TextBlock 的宽度,并以一种方式进行设置,使其在需要更改文本和/或字体族等时更新。为此,您需要将 Width 属性绑定到接受所有其他值的转换器。

从设置文本框宽度的样式开始:

<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>

现在添加值转换器本身的代码(请注意,我正在使用{{link1:Clarke Kent在另一个StackOverflow答案中发布的代码}}来测量文本):

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);
    }
}

但是在一个真正的应用程序中,我会尝试通过将它们放入网格或其他容器中来正确解决这个问题。


“…通过将它们放在网格或其他东西中。”那怎么做呢?那正是我正在寻找的,但我还没有想出一种方法来实现它。 - kcnygaard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接