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?
TextBlock
is inherently opposed to the concept of aligned children, but there are of course some possible hacks work-arounds:
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" TextBlock
s, 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();
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With