views:

614

answers:

3

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"&gt;
    <Grid>
        <StackPanel Orientation="Horizontal"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center">            
            <Label Content="Name:"/>
            <Label Content="Itzhak Perlman" FontSize="44"/>
        </StackPanel>
    </Grid>
</Window>

Renders the following:
alt text

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

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

+2  A: 

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:

Example Baseline Alignment

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);
Aviad P.
@אביעד, and what do I do with this value? what is actually the 'BaseLine'?
Shimmy
The baseline is the distance between the top of the text element and the line on which all letters 'sit'. If for example you set your `Top` margin to say 50 minus the baseline offset, you'll be guaranteed to align to a specific line no matter the font size.
Aviad P.
That's what I tried but doesn't work. I am still seeking for the solution. The problem is the space between the margin and 'top of the text' you mention. I updated my question. Thanks for all your effort אביעד.Any news?
Shimmy
Check my updated answer.
Aviad P.
Thank you so much Aviad. I'd like to make all my text controls (especially the TextBlocks to go thru this routine, is there a Xamly way to set it globally or I will need to inherit these controls? I thought let's ask the experts, you're tha man!
Shimmy
You have to use code behind, what's more you have to tweak the margin based on the specific control template of your text element. I.e. if it has borders, padding, etc. you have to modify the code behind to account for that.
Aviad P.
There is still kind of a problem, because if we could know the parent's actual height we could replace that '40' that is just a constant default with the dynamic heighst font size control.And the problem is cuz the ActualSize of the parent is rendered only after the children, nor can you iterate thru the TextBlock's siblings since when you do with the convert and the iterator is on the 1st child, the other siblings are not rendered yet and doesn't exist. Any good idea? I would really love to make this thing more dynamic, thanks for all.
Shimmy
The 40 is indeed just a constant offset for the baseline, you don't really need it, you can just substitute it for 0.
Aviad P.
Please vote: https://connect.microsoft.com/WPF/feedback/ViewFeedback.aspx?FeedbackID=523432
Shimmy
A: 

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.

Shimmy
That's a good way, but a converter is only going to take you part of the way, not the whole way, because for example, if the font size changes during the run of the program, the margin property won't get automatically reevaluated, you'll have to explicitly do that. Also, the converter needs to account for the other template elements such as padding, borders, etc, so you still need manual tweaking (converter parameter maybe).
Aviad P.
As you can see, I am passing the whole control to the converter, so no need for params.Anyway, I hope to improve it when I will have time.I want it should be based on the parent's height so it doesn't have to be a static value like '40'.
Shimmy
+1  A: 

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.

Erno de Weerd
Keep in mind that in many cases Labels are preferred to make styles easier to apply to labels without applying to TextBlocks. Given original posters example of 'Name: YOUR NAME HERE' it would be more proper to use a label to hold the "Name:" text.
jpierson