WPF - 左侧的可拉伸文本块,最大宽度和左侧溢出
Posted
技术标签:
【中文标题】WPF - 左侧的可拉伸文本块,最大宽度和左侧溢出【英文标题】:WPF - Stretchable TextBlock on the left with max width and overlflow to the left 【发布时间】:2020-10-11 21:37:20 【问题描述】:我想在 WPF 中的窗口中添加一个TextBlock
(或类似名称),但它必须具有以下属性:
TextBlock.Left == 0
与父级空间有关)
根据文字内容向右增长(随着文字增加,Width
增加,但Left
不变)
不要超出父级的宽度(伪:TextBlock.MaxWidth == Parent.Width
)
当文本长度超过最大宽度时,隐藏部分应该在左边! (没有换行!!!假设我有文本“Hi, this is a text”,可见部分应该是“...is a text”)。
我似乎无法将最大宽度设置为等于父级的大小...这不应该是一件容易的事吗?
我尝试了很多与网格、堆栈面板、文本对齐、FlowDirection、HorizontalAlignment 的组合,我尝试过的方法都不能解决这个问题,这应该很简单。我的文本要么从右侧开始,要么从左侧开始但溢出到右侧。
我怎样才能做到这一点? *** 上可用的解决方案似乎都有一个固定的最大宽度,这对我的解决方案来说是不行的,我必须让它跟随窗口。
【问题讨论】:
@NEBEZ,更新了问题的详细信息,我希望现在清楚了。 这能回答你的问题吗? Ellipsis at start of string in WPF ListView 【参考方案1】:创建一个名为 TextBlockTrimmer 的类并编写以下内容。
enum EllipsisPosition
Start,
Middle,
End
[DefaultProperty("Content")]
[ContentProperty("Content")]
internal class TextBlockTrimmer : ContentControl
private class TextChangedEventScreener : IDisposable
private readonly TextBlockTrimmer _textBlockTrimmer;
public TextChangedEventScreener(TextBlockTrimmer textBlockTrimmer)
_textBlockTrimmer = textBlockTrimmer;
s_textPropertyDescriptor.RemoveValueChanged(textBlockTrimmer.Content,
textBlockTrimmer.TextBlock_TextChanged);
public void Dispose()
s_textPropertyDescriptor.AddValueChanged(_textBlockTrimmer.Content,
_textBlockTrimmer.TextBlock_TextChanged);
private static readonly DependencyPropertyDescriptor s_textPropertyDescriptor =
DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
private const string ELLIPSIS = "...";
private static readonly Size s_inifinitySize = new Size(double.PositiveInfinity, double.PositiveInfinity);
public EllipsisPosition EllipsisPosition
get return (EllipsisPosition)GetValue(EllipsisPositionProperty);
set SetValue(EllipsisPositionProperty, value);
public static readonly DependencyProperty EllipsisPositionProperty =
DependencyProperty.Register("EllipsisPosition",
typeof(EllipsisPosition),
typeof(TextBlockTrimmer),
new PropertyMetadata(EllipsisPosition.End,
TextBlockTrimmer.OnEllipsisPositionChanged));
private static void OnEllipsisPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
((TextBlockTrimmer)d).OnEllipsisPositionChanged((EllipsisPosition)e.OldValue,
(EllipsisPosition)e.NewValue);
private string _originalText;
private Size _constraint;
protected override void OnContentChanged(object oldContent, object newContent)
var oldTextBlock = oldContent as TextBlock;
if (oldTextBlock != null)
s_textPropertyDescriptor.RemoveValueChanged(oldTextBlock, TextBlock_TextChanged);
if (newContent != null && !(newContent is TextBlock))
// ReSharper disable once LocalizableElement
throw new ArgumentException("TextBlockTrimmer access only TextBlock content", nameof(newContent));
var newTextBlock = (TextBlock)newContent;
if (newTextBlock != null)
s_textPropertyDescriptor.AddValueChanged(newTextBlock, TextBlock_TextChanged);
_originalText = newTextBlock.Text;
else
_originalText = null;
base.OnContentChanged(oldContent, newContent);
private void TextBlock_TextChanged(object sender, EventArgs e)
_originalText = ((TextBlock)sender).Text;
this.TrimText();
protected override Size MeasureOverride(Size constraint)
_constraint = constraint;
return base.MeasureOverride(constraint);
protected override Size ArrangeOverride(Size arrangeBounds)
var result = base.ArrangeOverride(arrangeBounds);
this.TrimText();
return result;
private void OnEllipsisPositionChanged(EllipsisPosition oldValue, EllipsisPosition newValue)
this.TrimText();
private IDisposable BlockTextChangedEvent()
return new TextChangedEventScreener(this);
private static double MeasureString(TextBlock textBlock, string text)
textBlock.Text = text;
textBlock.Measure(s_inifinitySize);
return textBlock.DesiredSize.Width;
private void TrimText()
var textBlock = (TextBlock)this.Content;
if (textBlock == null)
return;
if (DesignerProperties.GetIsInDesignMode(textBlock))
return;
var freeSize = _constraint.Width
- this.Padding.Left
- this.Padding.Right
- textBlock.Margin.Left
- textBlock.Margin.Right;
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (freeSize <= 0)
return;
using (this.BlockTextChangedEvent())
// this actually sets textBlock's text back to its original value
var desiredSize = TextBlockTrimmer.MeasureString(textBlock, _originalText);
if (desiredSize <= freeSize)
return;
var ellipsisSize = TextBlockTrimmer.MeasureString(textBlock, ELLIPSIS);
freeSize -= ellipsisSize;
var epsilon = ellipsisSize / 3;
if (freeSize < epsilon)
textBlock.Text = _originalText;
return;
var segments = new List<string>();
var builder = new StringBuilder();
switch (this.EllipsisPosition)
case EllipsisPosition.End:
TextBlockTrimmer.TrimText(textBlock, _originalText, freeSize, segments, epsilon, false);
foreach (var segment in segments)
builder.Append(segment);
builder.Append(ELLIPSIS);
break;
case EllipsisPosition.Start:
TextBlockTrimmer.TrimText(textBlock, _originalText, freeSize, segments, epsilon, true);
builder.Append(ELLIPSIS);
foreach (var segment in ((IEnumerable<string>)segments).Reverse())
builder.Append(segment);
break;
case EllipsisPosition.Middle:
var textLength = _originalText.Length / 2;
var firstHalf = _originalText.Substring(0, textLength);
var secondHalf = _originalText.Substring(textLength);
freeSize /= 2;
TextBlockTrimmer.TrimText(textBlock, firstHalf, freeSize, segments, epsilon, false);
foreach (var segment in segments)
builder.Append(segment);
builder.Append(ELLIPSIS);
segments.Clear();
TextBlockTrimmer.TrimText(textBlock, secondHalf, freeSize, segments, epsilon, true);
foreach (var segment in ((IEnumerable<string>)segments).Reverse())
builder.Append(segment);
break;
default:
throw new NotSupportedException();
textBlock.Text = builder.ToString();
private static void TrimText(TextBlock textBlock,
string text,
double size,
ICollection<string> segments,
double epsilon,
bool reversed)
while (true)
if (text.Length == 1)
var textSize = TextBlockTrimmer.MeasureString(textBlock, text);
if (textSize <= size)
segments.Add(text);
return;
var halfLength = Math.Max(1, text.Length / 2);
var firstHalf = reversed ? text.Substring(halfLength) : text.Substring(0, halfLength);
var remainingSize = size - TextBlockTrimmer.MeasureString(textBlock, firstHalf);
if (remainingSize < 0)
// only one character and it's still too large for the room, skip it
if (firstHalf.Length == 1)
return;
text = firstHalf;
continue;
segments.Add(firstHalf);
if (remainingSize > epsilon)
var secondHalf = reversed ? text.Substring(0, halfLength) : text.Substring(halfLength);
text = secondHalf;
size = remainingSize;
continue;
break;
然后按如下方式使用:
<local:TextBlockTrimmer EllipsisPosition="Start" Grid.Column="0">
<TextBlock Text="Excuse me but can I be you for a while"
TextTrimming="CharacterEllipsis" />
</local:TextBlockTrimmer>
取自here
【讨论】:
以上是关于WPF - 左侧的可拉伸文本块,最大宽度和左侧溢出的主要内容,如果未能解决你的问题,请参考以下文章