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本质上反对对齐孩子的概念,但当然有一些可能的黑客攻击:
- Fill in spaces to give the appearance of alignment.
- 填写空格以给出对齐的外观。
- Use
InlineUIContainer
to add actual UI elements (which you can align) inside the TextBlock. - 使用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" TextBlock
s, 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
它们可以包含和
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 theClientRectangle
of your base class in theDrawString 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本质上反对对齐孩子的概念,但当然有一些可能的黑客攻击:
- Fill in spaces to give the appearance of alignment.
- 填写空格以给出对齐的外观。
- Use
InlineUIContainer
to add actual UI elements (which you can align) inside the TextBlock. - 使用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" TextBlock
s, 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
它们可以包含和
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 theClientRectangle
of your base class in theDrawString 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.
我希望你是最好的。