TextBlock,其中一些文本左对齐,其余文本右对齐

时间:2021-05-19 12:36:47

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?

我知道如何使用两个文本块获得上述输出。但我想知道适用于单个文本块的行为是否相同?

3 个解决方案

#1


7  

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

TextBlock本质上反对对齐孩子的概念,但当然有一些可能的黑客攻击:

  1. Fill in spaces to give the appearance of alignment.
  2. 填写空格以给出对齐的外观。
  3. Use InlineUIContainer to add actual UI elements (which you can align) inside the TextBlock.
  4. 使用InlineUIContainer在TextBlock中添加实际的UI元素(可以对齐)。

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

我将通过创建具有LeftAlignedText和RightAlignedText属性的ExtendedTextBlock控件给出每个示例。用法是这样的:

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

1) Padding with spaces.

1)用空格填充。

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

2)使用InlineUIContainer添加对齐的文本

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中添加一个面板,它将负责对齐每个文本字符串。这是基本的想法:

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

因此,此版本的控件重新创建上述内容但隐藏了实现。它将InlineUIContainer控件添加到基本TextBlock,并保持对“左”和“右”TextBlocks的引用,根据需要设置其文本。

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();
    }
}

#2


3  

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.

TextBlock定义一个文本块,并将对齐属性应用于它。因为TextBlock和其他WPF元素自然是自动调整大小,所以使用其中两个元素的方法是正确的方法,每个方法都有不同的设置理由。

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

它们可以包含 元素,@ JMK指向代码教程

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.

要做你需要的,请考虑FlowDocument元素及其内容,这将允许你将理由描述为分层XAML标记。 FlowDocument可以占用非常少量的屏幕空间。

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.

或者,考虑使用FormattedText类实现一个转换器,在该转换器中,您可以通过添加空格和冒号来发现TextBlock的宽度和要转换的字符串的宽度,并相应地调整字符串中的间距。

#3


1  

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:

如果您正在使用(或愿意使用)固定宽度的字体,则可以使用String.PadRight。例如,如果TextBox的最大文本长度为30个字符,请调用:

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:

无论左对齐的字符串长度如何,这将使冒号对齐。没有办法左右对齐TextBox。我不是WPF的人,所以我的下一个建议还涉及从Windows Forms指令转换为WPF equivelents。无论如何,如果你必须使用可变宽度字体,你可以做的另一件事就是这样做:

  • Create a class that derives from TextBox.
  • 创建一个派生自TextBox的类。
  • Override the OnPaint function, or WPF equivelent.
  • 覆盖OnPaint函数或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.
  • 使用Graphics.DrawString(或equivelent)左对齐主字符串,然后右对齐冒号。在两种情况下都使用DrawString函数中的基类的ClientRectangle。

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

除了使用自定义OnPaint函数创建派生类之外,您还必须使用某种技巧。

I wish you the best.

我希望你是最好的。

#1


7  

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

TextBlock本质上反对对齐孩子的概念,但当然有一些可能的黑客攻击:

  1. Fill in spaces to give the appearance of alignment.
  2. 填写空格以给出对齐的外观。
  3. Use InlineUIContainer to add actual UI elements (which you can align) inside the TextBlock.
  4. 使用InlineUIContainer在TextBlock中添加实际的UI元素(可以对齐)。

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

我将通过创建具有LeftAlignedText和RightAlignedText属性的ExtendedTextBlock控件给出每个示例。用法是这样的:

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

1) Padding with spaces.

1)用空格填充。

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

2)使用InlineUIContainer添加对齐的文本

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中添加一个面板,它将负责对齐每个文本字符串。这是基本的想法:

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

因此,此版本的控件重新创建上述内容但隐藏了实现。它将InlineUIContainer控件添加到基本TextBlock,并保持对“左”和“右”TextBlocks的引用,根据需要设置其文本。

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();
    }
}

#2


3  

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.

TextBlock定义一个文本块,并将对齐属性应用于它。因为TextBlock和其他WPF元素自然是自动调整大小,所以使用其中两个元素的方法是正确的方法,每个方法都有不同的设置理由。

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

它们可以包含 元素,@ JMK指向代码教程

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.

要做你需要的,请考虑FlowDocument元素及其内容,这将允许你将理由描述为分层XAML标记。 FlowDocument可以占用非常少量的屏幕空间。

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.

或者,考虑使用FormattedText类实现一个转换器,在该转换器中,您可以通过添加空格和冒号来发现TextBlock的宽度和要转换的字符串的宽度,并相应地调整字符串中的间距。

#3


1  

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:

如果您正在使用(或愿意使用)固定宽度的字体,则可以使用String.PadRight。例如,如果TextBox的最大文本长度为30个字符,请调用:

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:

无论左对齐的字符串长度如何,这将使冒号对齐。没有办法左右对齐TextBox。我不是WPF的人,所以我的下一个建议还涉及从Windows Forms指令转换为WPF equivelents。无论如何,如果你必须使用可变宽度字体,你可以做的另一件事就是这样做:

  • Create a class that derives from TextBox.
  • 创建一个派生自TextBox的类。
  • Override the OnPaint function, or WPF equivelent.
  • 覆盖OnPaint函数或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.
  • 使用Graphics.DrawString(或equivelent)左对齐主字符串,然后右对齐冒号。在两种情况下都使用DrawString函数中的基类的ClientRectangle。

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

除了使用自定义OnPaint函数创建派生类之外,您还必须使用某种技巧。

I wish you the best.

我希望你是最好的。