Align bottoms of text in controls
The following snippet:
<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>
Renders the following:
Is there any way I can set in the Labels' styles so that their text bottoms should be aligned?
I have the same question with TextBlocks as 开发者_StackOverflowwell.NOTE: since I've been struggling with this issue for a while, please post only certains answers that you know that work.
I already tried: VerticalAlignment, VerticalContentAlignment, Padding, Margin. Is there anything else I am not aware of?I've read this post, but it doesn't talk about a scenario of different font size.
UPDATE: The problem is, that even Padding is set to 0 there is still an indeterminate space around the font, within the ContentPresenter area. this space varies on the font size. If I could control this space I would be in a better situation.
Thanks
Another fairly simple solution:
1) Use TextBlock controls instead of Labels. The reason being that TextBlock is lighter weight than Label - see http://joshsmithonwpf.wordpress.com/2007/07/04/differences-between-label-and-textblock/
2) Use the LineHeight and LineStackingStrategy = BlockLineHeight for your TextBlock style. This will align the two at their baseline easily.
<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>
I really like the creative solutions that are presented here but I do think that in the long run (pun intended) we should use this:
<TextBlock>
<Run FontSize="20">What</Run>
<Run FontSize="36">ever</Run>
<Run FontSize="12" FontWeight="Bold">FontSize</Run>
</TextBlock>
The only thing that is missing from the Run element is databinding of the Text property but that might be added sooner or later.
A Run will not fix the alignment of labels and their textboxes but for many simple situation the Run will do quite nicely.
There is no XAML only solution, you have to use code behind. Also, even with code-behind, there's no general solution for this, because what if your text is multi-line? Which baseline should be used in that case? Or what if there are multiple text elements in your template? Such as a header and a content, or more, which baseline then?
In short, your best bet is to align the text manually using top/bottom margins.
If you're willing to make the assumption that you have a single text element, you can figure out the pixel distance of the baseline from the top of the element by instantiating a FormattedText
object with all the same properties of the existing text element. The FormattedText
object has a double
Baseline
property which holds that value. Note that you still would have to manually enter a margin, because the element might not sit exactly against the top or bottom of its container.
See this MSDN forum post: Textbox Baseline
Here's a method I wrote that extracts that value. It uses reflection to get the relevant properties because they are not common to any single base class (they are defined separately on Control
, TextBlock
, Page
, TextElement
and maybe others).
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;
}
EDIT: Shimmy, in response to your comment, I don't believe you've actually tried this solution, because it works. Here's an example:
Here's the 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>
And here's the code behind that achieves this
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);
<TextBlock>
<InlineUIContainer BaselineAlignment="Baseline"><TextBlock>Small</TextBlock></InlineUIContainer>
<InlineUIContainer BaselineAlignment="Baseline"><TextBlock FontSize="50">Big</TextBlock></InlineUIContainer>
</TextBlock>
This should works well. Experiment with Baseline/Bottom/Center/Top.
XAML designer supports aligning TextBlock
controls by baseline at design time:
This assigns fixed margins to your controls. As long as font sizes do not change at run time, the alignment will be preserved.
I actually found a simple answer based on Aviad's.
I created a converter that contains Aviad's function that accepts the element itself and returns calculated Thickness.
Then I set up
<Style TargetType="TextBlock">
<Setter Property="Margin"
Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource converters:TextAlignmentMarginConverter}}" />
</Style>
The disadvantage is that this is obviously occupies the original Margin property, since a TextBlock doesn't have a template so we can't set it via TemplateBinding.
In the blog article XAML text improvements in Windows 8.1 is explained how you can align two TextBlock
s of different font sizes by their baseline. The trick is TextLineBounds="TrimToBaseline"
combined with VerticalAlignment="Bottom"
. That removes the size below their baseline, and then moves the TextBlock
s down. You can then move it them back up to the desired height by setting a Margin
on a container you put them in.
Sample:
<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>
精彩评论