【问题标题】:Align bottoms of text in controls对齐控件中文本的底部
【发布时间】:2011-01-03 09:41:57
【问题描述】:

下面的sn-p:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <StackPanel Orientation="Horizontal"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center">            
            <Label Content="Name:"/>
            <Label Content="Itzhak Perlman" FontSize="44"/>
        </StackPanel>
    </Grid>
</Window>

呈现以下内容:

有什么方法可以设置标签的样式,使其文本底部对齐?
我对 TextBlocks 也有同样的问题。

注意:由于我一直在为这个问题苦苦挣扎,请仅发布您知道有效的某些答案。
我已经尝试过:VerticalAlignment、VerticalContentAlignment、Padding、Margin。还有什么我不知道的吗?

我已经阅读了this 的帖子,但它没有谈论不同字体大小的场景。

更新:问题是,即使 Padding 设置为 0,在 ContentPresenter 区域内,字体周围仍有一个不确定的空间。这个空间因字体大小而异。 如果我能控制这个空间,我的处境会更好。

谢谢

【问题讨论】:

  • 啊哈!您的更新澄清了问题。

标签: wpf layout textblock


【解决方案1】:

另一个相当简单的解决方案:

1) 使用 TextBlock 控件而不是 Labels。原因是 TextBlock 比 Label 更轻 - 请参阅 http://joshsmithonwpf.wordpress.com/2007/07/04/differences-between-label-and-textblock/

2) 使用 LineHeight 和 LineStackingStrategy = BlockLineHeight 作为您的 TextBlock 样式。这将很容易将两者对齐到它们的基线。

<StackPanel Orientation="Horizontal"
            VerticalAlignment="Center"
            HorizontalAlignment="Center">
    <StackPanel.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="LineHeight" Value="44pt"/>
            <Setter Property="LineStackingStrategy" Value="BlockLineHeight"/>
        </Style>
    </StackPanel.Resources>            
    <TextBlock Text="Name:"/>
    <TextBlock Text="Itzhak Perlman" FontSize="44"/>
</StackPanel>

【讨论】:

  • @Shimmy 你试过这个吗?它简单有效,应该是答案。很适合我。
【解决方案2】:

我真的很喜欢这里提出的创造性解决方案,但我确实认为从长远来看(双关语)我们应该使用这个:

<TextBlock>
   <Run FontSize="20">What</Run>
   <Run FontSize="36">ever</Run>
   <Run FontSize="12" FontWeight="Bold">FontSize</Run>
</TextBlock>

Run 元素中唯一缺少的是 Text 属性的数据绑定,但迟早会添加。

Run 不会修复标签及其文本框的对齐方式,但对于许多简单的情况,Run 会做得很好。

【讨论】:

  • 请记住,在许多情况下,首选标签是为了使样式更容易应用于标签而不应用于 TextBlocks。鉴于“姓名:您的姓名”的原始海报示例,使用标签来保存“姓名:”文本会更合适。
【解决方案3】:

没有唯一的 XAML 解决方案,您必须使用后面的代码。此外,即使使用代码隐藏,也没有通用的解决方案,因为如果您的文本是多行的怎么办?在这种情况下应该使用哪个基线?或者如果您的模板中有多个文本元素怎么办?比如一个header和一个content,或者更多,那是哪个baseline呢?

简而言之,最好的办法是使用顶部/底部边距手动对齐文本。

如果您愿意假设您有一个文本元素,您可以通过实例化具有所有相同属性的FormattedText 对象来计算基线到元素顶部的像素距离现有的文本元素。 FormattedText 对象有一个 double Baseline 属性来保存该值。请注意,您仍然需要手动输入边距,因为元素可能不会完全靠在其容器的顶部或底部。

请参阅此 MSDN 论坛帖子:Textbox Baseline

这是我编写的提取该值的方法。它使用反射来获取相关属性,因为它们对任何单个基类都不通用(它们分别在ControlTextBlockPageTextElement 和其他人上定义)。

public double CalculateBaseline(object textObject)
{
    double r = double.NaN;
    if (textObject == null) return r;

    Type t = textObject.GetType();
    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;

    var fontSizeFI = t.GetProperty("FontSize", bindingFlags);
    if (fontSizeFI == null) return r;
    var fontFamilyFI = t.GetProperty("FontFamily", bindingFlags);
    var fontStyleFI = t.GetProperty("FontStyle", bindingFlags);
    var fontWeightFI = t.GetProperty("FontWeight", bindingFlags);
    var fontStretchFI = t.GetProperty("FontStretch", bindingFlags);

    var fontSize = (double)fontSizeFI.GetValue(textObject, null);
    var fontFamily = (FontFamily)fontFamilyFI.GetValue(textObject, null);
    var fontStyle = (FontStyle)fontStyleFI.GetValue(textObject, null);
    var fontWeight = (FontWeight)fontWeightFI.GetValue(textObject, null);
    var fontStretch = (FontStretch)fontStretchFI.GetValue(textObject, null);

    var typeFace = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);

    var formattedText = new FormattedText(
        "W", 
        CultureInfo.CurrentCulture, 
        FlowDirection.LeftToRight, 
        typeFace, 
        fontSize, 
        Brushes.Black);

    r = formattedText.Baseline;

    return r;
}

编辑:Shimmy,作为对您的评论的回应,我认为您实际上并未尝试过此解决方案,因为它有效。这是一个例子:

这是 XAML:

<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Margin" Value="0,40,0,0"/>
        </Style>
    </StackPanel.Resources>
    <StackPanel Orientation="Horizontal">
        <TextBlock Name="tb1" Text="Lorem " FontSize="10"/>
        <TextBlock Name="tbref" Text="ipsum"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBlock Name="tb2" Text="dolor "  FontSize="20"/>
        <TextBlock Text="sit"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBlock Name="tb3" Text="amet "  FontSize="30"/>
        <TextBlock Text="consectetuer"/>
    </StackPanel>
</StackPanel>

这是实现这一目标的代码

double baseRef = CalculateBaseline(tbref);
double base1 = CalculateBaseline(tb1) - baseRef;
double base2 = CalculateBaseline(tb2) - baseRef;
double base3 = CalculateBaseline(tb3) - baseRef;
tb1.Margin = new Thickness(0, 40 - base1, 0, 0);
tb2.Margin = new Thickness(0, 40 - base2, 0, 0);
tb3.Margin = new Thickness(0, 40 - base3, 0, 0);

【讨论】:

  • @אביעד,我该如何处理这个值?什么是“基线”?
  • 基线是文本元素顶部和所有字母“坐”的那一行之间的距离。例如,如果您将 Top 边距设置为 50 减去基线偏移量,则无论字体大小如何,您都可以保证与特定行对齐。
  • 这就是我尝试但不起作用的方法。我仍在寻找解决方案。问题是您提到的边距和“文本顶部”之间的空间。我更新了我的问题。感谢您的所有努力 אביעד。有消息吗?
  • 非常感谢 Aviad。我想制作我所有的文本控件(尤其是通过这个例程的 TextBlocks,有没有一种 Xamly 方法可以全局设置它,或者我需要继承这些控件?我想让我们问问专家,你就是那个人!
  • 你必须使用后面的代码,更重要的是你必须根据你的文本元素的特定控件模板来调整边距。 IE。如果它有边框、填充等,你必须修改后面的代码来解决这个问题。
【解决方案4】:
<TextBlock>
<InlineUIContainer BaselineAlignment="Baseline"><TextBlock>Small</TextBlock></InlineUIContainer>
<InlineUIContainer BaselineAlignment="Baseline"><TextBlock FontSize="50">Big</TextBlock></InlineUIContainer>
</TextBlock>

这应该很好用。试验基线/底部/中心/顶部。

【讨论】:

    【解决方案5】:

    XAML 设计器支持在设计时按基线对齐TextBlock 控件:

    这会为您的控件分配固定边距。只要字体大小在运行时不改变,对齐就会被保留。

    【讨论】:

    • 是的,这行得通。也许在提出问题时(2010 年)此功能不可用,但我确认可以在 Viusal Studio 2015 的 WPF 设计器中以这种方式完成对齐(并且显然在此答案的日期所示的一些早期版本中也是如此)。两个调整后的对象必须包含文本,所以如果您要对齐,例如标签和文本框(通常为空),在其中临时添加一些文本。
    【解决方案6】:

    其实我根据Aviad's 找到了一个简单的答案。

    我创建了一个包含 Aviad 函数的转换器,该函数接受元素本身并返回计算出的厚度。

    然后我设置

    <Style TargetType="TextBlock">
        <Setter Property="Margin" 
            Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource converters:TextAlignmentMarginConverter}}" />
    </Style>
    

    缺点是这显然占用了原来的 Margin 属性,因为 TextBlock 没有模板所以我们不能通过 TemplateBinding 设置它。

    【讨论】:

    • 这是一个好方法,但是转换器只会带你走一部分,而不是全部,因为例如,如果在程序运行期间字体大小发生变化,边距属性不会自动重新评估,您必须明确地这样做。此外,转换器需要考虑其他模板元素,例如填充、边框等,因此您仍然需要手动调整(可能是转换器参数)。
    • 如您所见,我将整个控制权传递给转换器,因此不需要参数。无论如何,我希望有时间时改进它。我希望它应该基于父母的身高,所以它不必是像“40”这样的静态值。
    【解决方案7】:

    在博客文章XAML text improvements in Windows 8.1 中解释了如何将两个不同字体大小的TextBlocks 与它们的基线对齐。诀窍是TextLineBounds="TrimToBaseline" 结合VerticalAlignment="Bottom"。这将删除低于其基线的大小,然后将TextBlocks 向下移动。然后,您可以通过在放置它们的容器上设置 Margin 将它们移回所需的高度。

    示例:

    <Grid Margin="some margin to lift the TextBlocks to desired height">
        <TextBlock Text="{x:Bind ViewModel.Name, Mode=OneWay}"
                    Style="{StaticResource HeaderTextBlockStyle}"
                    VerticalAlignment="Bottom"
                    TextLineBounds="TrimToBaseline" />
        <TextBlock Text="{x:Bind ViewModel.Description.Yield, Mode=OneWay}"
                    Style="{StaticResource SubheaderTextBlockStyle}"
                    VerticalAlignment="Bottom"
                    HorizontalAlignment="Right"
                    TextLineBounds="TrimToBaseline" />
    </Grid>
    

    【讨论】:

      猜你喜欢
      • 2013-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-08
      • 2013-06-11
      • 2012-01-14
      相关资源
      最近更新 更多