在控件中对齐文本底部

20
以下代码片段:
<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>

渲染如下:
alt text

有没有办法设置标签的样式,使它们的文本底部对齐?
我对TextBlocks也有同样的问题。

注意:由于我已经为此问题苦苦挣扎了一段时间,请只发布您知道有效的答案。
我已经尝试过:VerticalAlignment、VerticalContentAlignment、Padding、Margin。还有其他我不知道的方法吗?

我已经阅读了这篇文章,但它并未讨论不同字体大小的情况。

更新:问题在于,即使将Padding设置为0,ContentPresenter区域中仍然存在一个不确定的空间。这个空间随着字体大小而变化。如果我能控制这个空间,我会处于更好的情况。

谢谢


1
啊哈!你的更新澄清了问题。 - Steve Psaltis
7个回答

30

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

1)使用TextBlock控件替代Label。原因是TextBlock比Label更轻巧-见http://joshsmithonwpf.wordpress.com/2007/07/04/differences-between-label-and-textblock/

2)在TextBlock样式中使用LineHeight和LineStackingStrategy = BlockLineHeight。这将轻松地使两者在其基线上对齐。

<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你试过这个吗? 这很简单且有效,应该是答案。对我很有帮助。 - ygoe

7

我非常喜欢这里提出的创意解决方案,但我认为从长远来看(双关语),我们应该使用这个:

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

Run元素唯一缺少的是Text属性的数据绑定,但这可能会在不久的将来添加。

Run无法修复标签和文本框之间的对齐问题,但对于许多简单情况,Run将非常适用。


请记住,在许多情况下,标签比文本块更受欢迎,因为它们可以更轻松地应用样式而不必应用到文本块。鉴于原始海报的示例“名称:YOUR NAME HERE”,更适合使用标签来容纳“名称:”文本。 - jpierson

6

没有仅使用XAML的解决方案,您必须使用代码后台。即使使用代码后台,也没有通用解决方案,因为如果您的文本是多行的,会怎样?在这种情况下应该使用哪个基线?或者如果您的模板中有多个文本元素呢?例如标题和内容等,那么应该使用哪个基线呢?

简而言之,最好的方法是使用上/下边距手动对齐文本。

如果您愿意做出这样的假设,即您只有一个文本元素,那么您可以通过使用与现有文本元素完全相同的属性实例化FormattedText对象来找出基线距离元素顶部的像素距离。 FormattedText对象具有一个doubleBaseline属性,其中包含该值。注意,您仍然需要手动输入边距,因为元素可能无法完全靠近其容器的顶部或底部。

请参阅此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);

@אביעד,我该如何处理这个值?实际上,“BaseLine”是什么? - Shimmy Weitzhandler
基准线是文本元素顶部与所有字母“坐在”上面的线之间的距离。例如,如果您将Top边距设置为基线偏移量减去50,则无论字体大小如何,都可以确保对齐到特定行。 - Aviad P.
这就是我尝试过的,但不起作用。我仍在寻找解决方案。问题在于您提到的边距和“文本顶部”之间的空格。我更新了我的问题。感谢您的所有努力,אביעד。 有任何消息吗? - Shimmy Weitzhandler
非常感谢Aviad。我想让所有我的文本控件(特别是TextBlocks)都通过此例程,是否有一种Xamly方式可以全局设置,或者我需要继承这些控件?我想问问专家,你是那个人! - Shimmy Weitzhandler
你必须使用代码后台,而且还要根据文本元素的特定控件模板调整边距。例如,如果它有边框、填充等,你必须修改代码以考虑这些因素。 - Aviad P.
显示剩余3条评论

3
<TextBlock>
<InlineUIContainer BaselineAlignment="Baseline"><TextBlock>Small</TextBlock></InlineUIContainer>
<InlineUIContainer BaselineAlignment="Baseline"><TextBlock FontSize="50">Big</TextBlock></InlineUIContainer>
</TextBlock>

这应该能很好地工作。尝试使用基线/底部/居中/顶部进行实验。


1

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

enter image description here

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


是的,这个方法可行。也许在问题被提出的时候(2010年)这个功能还没有出现,但我确认在Visual Studio 2015的WPF设计器中可以通过这种方式进行对齐(显然,在此答案发布之前的某些早期版本中也可以)。两个调整后的对象必须包含文本,因此如果您要对齐例如Label和TextBox(通常为空),请在其中暂时添加一些文本。 - miroxlav

0

实际上我找到了一个基于Aviad的简单答案。

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

然后我进行设置。

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

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


这是一个不错的方法,但转换器只能帮你完成部分工作,而非全部工作。例如,如果程序运行期间字体大小发生变化,边距属性不会自动重新评估,你需要显式地进行调整。此外,转换器还需要考虑其他模板元素,如填充、边框等,因此仍然需要手动微调(也许可以使用转换器参数)。 - Aviad P.
正如您所看到的,我正在将整个控制权交给转换器,因此不需要参数。 无论如何,我希望在有时间时能够改进它。 我希望它应该基于父元素的高度,这样它就不必是静态值,比如“40”。 - Shimmy Weitzhandler

0
在博客文章Windows 8.1中的XAML文本改进中解释了如何通过基线对齐不同字体大小的两个TextBlock。诀窍是将TextLineBounds="TrimToBaseline"VerticalAlignment="Bottom"相结合。这会删除其基线下方的大小,然后将TextBlock向下移动。然后,您可以通过在放置它们的容器上设置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>

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