Khushi
Khushi

Reputation: 1051

TextBlock with some text as left aligned and the remaining text as right aligned

I want to have a textblock which contains text like below:

My associated textbox is                :

Text is left-aligned and colon is right aligned.

I know how to get the above output using two textblocks. But I want to know that is the same behavior applicable to a single textblock?

Upvotes: 7

Views: 3937

Answers (3)

drankin2112
drankin2112

Reputation: 4804

If you're using (or willing to use) a fixed-width font, you can use String.PadRight. For instance, if the maximum text length for the TextBox is 30 chars, call:

myTextBox.Text = myString.PadRight(29, ' ') + ":";

That will make the colon align right regardless of the left aligned string length. There is no way to both left and right align a TextBox. I'm not a WPF guy so my next suggestion would also involve translating to WPF equivelents from Windows Forms instructions. Regardless though, another thing you could do if you have to use a variable-width font would be to do this:

  • Create a class that derives from TextBox.
  • Override the OnPaint function, or WPF equivelent.
  • Create code to fill the background and borders how ever you want.
  • Use Graphics.DrawString (or equivelent) aligned left for the main string, and then aligned right for the colon. In both cases using the ClientRectangle of your base class in the DrawString function.

Aside from creating a derived class with a custom OnPaint function, you're gonna have to use some sort of trickery.

I wish you the best.

Upvotes: 1

McGarnagle
McGarnagle

Reputation: 102723

TextBlock is inherently opposed to the concept of aligned children, but there are of course some possible hacks work-arounds:

  1. Fill in spaces to give the appearance of alignment.
  2. Use InlineUIContainer to add actual UI elements (which you can align) inside the TextBlock.

I'll give an example of each by creating an ExtendedTextBlock control, with LeftAlignedText and RightAlignedText properties. Usage is like this:

<my:ExtendedTextBlock RightAlignedText=":" LeftAlignedText="My associated textbox is" />

1) Padding with spaces.

For this approach, I've borrowed from this answer to get the actual width of a text string. The idea is basically, subtract the total width of the text from the actual width of the control, and insert the appropriate number of spaces between them.

public class ExtendedTextBlock : TextBlock
{
    public string RightAlignedText
    {
        get { return (string)GetValue(RightAlignedTextProperty); }
        set { SetValue(RightAlignedTextProperty, value); }
    }
    public static readonly DependencyProperty RightAlignedTextProperty =
        DependencyProperty.Register("RightAlignedText", typeof(string), typeof(ExtendedTextBlock), new PropertyMetadata(SetText));

    public string LeftAlignedText
    {
        get { return (string)GetValue(LeftAlignedTextProperty); }
        set { SetValue(LeftAlignedTextProperty, value); }
    }
    public static readonly DependencyProperty LeftAlignedTextProperty =
        DependencyProperty.Register("LeftAlignedText", typeof(string), typeof(ExtendedTextBlock), new PropertyMetadata(SetText));

    public static void SetText(object sender, DependencyPropertyChangedEventArgs args)
    {
        SetText((ExtendedTextBlock)sender);
    }

    public static void SetText(ExtendedTextBlock owner)
    {
        if (owner.ActualWidth == 0)
            return;

        // helper function to get the width of a text string
        Func<string, double> getTextWidth = text =>
        {
            var formattedText = new FormattedText(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight,
                new Typeface(owner.FontFamily, owner.FontStyle, owner.FontWeight, owner.FontStretch),
                owner.FontSize,
                Brushes.Black);
            return formattedText.Width;
        };

        // calculate the space needed to fill in
        double spaceNeeded = owner.ActualWidth - getTextWidth(owner.LeftAlignedText ?? "") - getTextWidth(owner.RightAlignedText ?? "");

        // get the width of an empty space (have to cheat a bit since the width of an empty space returns zero)
        double spaceWidth = getTextWidth(" .") - getTextWidth(".");
        int spaces = (int)Math.Round(spaceNeeded / spaceWidth);

        owner.Text = owner.LeftAlignedText + new string(Enumerable.Repeat(' ', spaces).ToArray()) + owner.RightAlignedText;
    }

    public ExtendedTextBlock()
    {
        SizeChanged += (sender, args) => SetText(this);
    }
}

2) Using InlineUIContainer to add aligned text

The idea here is to add a panel, inside the TextBlock, which will be responsible for aligning each text string. This is the basic idea:

<TextBlock>
    <InlineUIContainer>
        <Grid Width="{Binding RelativeSource={RelativeSource AncestorType=TextBlock},Path=ActualWidth}">
            <TextBlock Text="Hello" />
            <TextBlock Text="World" TextAlignment="Right" />
        </Grid>
    </InlineUIContainer>
</TextBlock>

So, this version of the control re-creates the above but hides the implementation. It adds the InlineUIContainer control to the base TextBlock, and keeps a reference to the "left" and "right" TextBlocks, setting their text as needed.

public class ExtendedTextBlock2 : TextBlock
{
    private TextBlock _left, _right;

    public string RightAlignedText
    {
        get { return (string)GetValue(RightAlignedTextProperty); }
        set { SetValue(RightAlignedTextProperty, value); }
    }
    public static readonly DependencyProperty RightAlignedTextProperty =
        DependencyProperty.Register("RightAlignedText", typeof(string), typeof(ExtendedTextBlock2), new PropertyMetadata(SetText));

    public string LeftAlignedText
    {
        get { return (string)GetValue(LeftAlignedTextProperty); }
        set { SetValue(LeftAlignedTextProperty, value); }
    }
    public static readonly DependencyProperty LeftAlignedTextProperty =
        DependencyProperty.Register("LeftAlignedText", typeof(string), typeof(ExtendedTextBlock2), new PropertyMetadata(SetText));

            public static void SetText(object sender, DependencyPropertyChangedEventArgs args)
    {
        ((ExtendedTextBlock2)sender).SetText();
    }

    public void SetText()
    {
        if (_left == null || _right == null)
            return;
        _left.Text = LeftAlignedText;
        _right.Text = RightAlignedText;
    }

    public ExtendedTextBlock2()
    {
        Loaded += ExtendedTextBlock2_Loaded;
    }

    void ExtendedTextBlock2_Loaded(object sender, RoutedEventArgs e)
    {
        Inlines.Clear();
        var child = new InlineUIContainer();
        var container = new Grid();
        var widthBinding = new Binding { Source = this, Path = new PropertyPath(TextBlock.ActualWidthProperty) };
        container.SetBinding(Grid.WidthProperty, widthBinding);
        child.Child = container;
        container.Children.Add(_left = new TextBlock { HorizontalAlignment = System.Windows.HorizontalAlignment.Left });
        container.Children.Add(_right = new TextBlock { HorizontalAlignment = System.Windows.HorizontalAlignment.Right });
        Inlines.Add(child);

        SetText();
    }
}

Upvotes: 8

Rob Perkins
Rob Perkins

Reputation: 3130

It is not. (I looked for quite a while.)

A TextBlock defines one block of text, and applies justification properties to it. Because TextBlock and other WPF elements are naturally auto-sizing, the approach of using two of them, each with different settings on their justification proprty, is the correct approach.

They can contain <Span> and <Run> elements, and @JMK pointed to a code tutorial

To do what you need, consider the FlowDocument element and its contents, which will permit you to describe justifications as hierarchical XAML markup. A FlowDocument can consume a very small amount of screen space.

Or, consider implementing a converter in which you discover the width of your TextBlock and the width of a string you intend to transform by adding spaces and that colon, and adjust the spacing within your string accordingly, using the FormattedText class.

Upvotes: 3

Related Questions