I have a StackPanel with several TextBlocks in it, like this:
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="First is quite lengthy" />
<TextBlock Text="This one's shorter"/>
<TextBlock Text="This one too"/>
<TextBlock Text="This one is a bit longer again"/>
</StackPanel>
I want to justify all of them, so that both the left sides and the right sides of the TextBlocks are aligned vertically - how can I do that?
Here's what the above code looks like:
Rough estimation of what I want to achieve:
I have tried experimenting with TextAlignment (Justify does not work for me), FontStretch, HorizontalAlignment, DockPanel and Viewbox (which does not work since I want to keep the font size constant). Is there anything I can do to achieve this without hard-coding some spaces inside of the texts?
CodePudding user response:
I won't lie, it wasn't too long ago when I first picked up WPF, so I apologize if my answer doesn't exactly have the best solution. Anyway, I think the only option you quite have is making different words as different Text Blocks and playing around with the margin, and alignment settings. Otherwise, as far as I know, I might've misunderstood the question. Sorry if I have.
CodePudding user response:
I was surprised to find out WPF doesn't support justified single line TextBlocks by default. However, you can relatively easily achieve this behaviour yourself. I've cobbled together a quick solution that provides a UserControl with the single purpose of drawing a justified single line of text.
The solution is very simple, doesn't support text-wrapping, margins or any other special layout cases and only has the bare bones DependencyProperties. It should be a good starting point though.
public partial class JustifiedLine : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(JustifiedLine),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty MinSpaceWidthProperty = DependencyProperty.Register(
"MinSpaceWidth",
typeof(double),
typeof(JustifiedLine),
new FrameworkPropertyMetadata(5.0, FrameworkPropertyMetadataOptions.AffectsRender));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public double MinSpaceWidth
{
get => (double)GetValue(MinSpaceWidthProperty);
set => SetValue(MinSpaceWidthProperty, value);
}
public JustifiedLine()
{
InitializeComponent();
}
protected override void OnRender(DrawingContext drawingContext)
{
string[] words = Text.Split(' ');
if (words.Length == 0)
return;
int numValidWords = 0;
double totalWordsWidth = 0;
/* add widths of all words */
for (int i = 0; i < words.Length; i )
{
if (String.IsNullOrWhiteSpace(words[i]))
continue;
FormattedText fText = MakeFormattedText(words[i]);
totalWordsWidth = fText.Width;
numValidWords ;
}
double fullWidth = GetFullWidth();
double spaceWidth = (fullWidth - totalWordsWidth) / (numValidWords - 1);
if (spaceWidth < MinSpaceWidth)
spaceWidth = MinSpaceWidth;
double currentWidth = 0;
for (int i = 0; i < words.Length; i )
{
if (String.IsNullOrWhiteSpace(words[i]))
continue;
FormattedText fText = MakeFormattedText(words[i]);
drawingContext.DrawText(fText, new Point(currentWidth, 0));
currentWidth = fText.Width spaceWidth;
/* if Height isn't specified, automatically set from text height */
if (double.IsNaN(Height))
Height = fText.Height;
}
}
/* return either width of this control, or the parent control if not set. If neither is set, return 0.0 */
private double GetFullWidth()
{
if (!double.IsNaN(Width))
return Width;
FrameworkElement parent = Parent as FrameworkElement;
if (parent != null && !double.IsNaN(parent.ActualWidth))
return parent.ActualWidth;
return 0.0;
}
/* might want to bind font etc. to dependency property, these here are just placeholders */
private FormattedText MakeFormattedText(string text)
{
return new FormattedText(
text,
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface("Verdana"),
16,
Brushes.White,
VisualTreeHelper.GetDpi(this).PixelsPerDip);
}
}
if used like this:
<StackPanel Width="300">
<usercontrols:JustifiedLine Text="First is quite lengthy"/>
<usercontrols:JustifiedLine Text="This one's shorter"/>
<usercontrols:JustifiedLine Text="This one too"/>
<usercontrols:JustifiedLine Text="This one is a bit longer again"/>
</StackPanel>
xmlns:usercontrols="clr-namespace:MyProject.controls.usercontrols"
This is my result: