在WPF ListView中字符串开头的省略号

我有一个WPF ListViewGridView ),单元格模板包含一个TextBlock 。 如果我在TextBlock上添加: TextTrimming="CharacterEllipsis" TextWrapping="NoWrap" ,当列小于字符串的长度时,省略号将出现在我的字符串的末尾。 我需要的是在字符串的开头有省略号。

即如果我有字符串Hello World! ,我想...lo World! 而不是Hello W...

有任何想法吗?

您可以尝试使用ValueConverter(参见IValueConverter接口 )来更改应自己在列表框中显示的字符串。 也就是说,在Convert方法的实现中,您将测试字符串是否长于可用空间,然后将它们更改为…加上字符串的右侧。

我遇到了同样的问题,写了一个附加属性来解决这个问题(或者说,提供这个function)。 在这里捐赠我的代码:

用法

    

不要忘记在Page / Window / UserControl根目录中添加名称空间声明:

 xmlns:controls="clr-namespace:Hillinworks.Wpf.Controls" 

TextBlockTrimmer.EllipsisPosition可以是StartMiddle (mac style)或End 。 很确定你可以找出他们名字中的哪一个。

TextBlockTrimmer.cs

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Markup; namespace Hillinworks.Wpf.Controls { 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(); 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)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)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 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; } } } } 

不幸的是, 从文档中可以看出,这在今天的WPF中是不可能的 。

(我以前在微软的WPF上工作过,这是我们遗憾的一个function – 不确定它是否计划用于未来的版本)

我实现(复制)上面的TextBlockTrimmer代码,它非常适合加载,但如果绑定到更改的View Model属性, TextBlock.Text将不会更新。 我发现有用的是

  1. TextBlockTrimmer定义一个名为TextBlockText的DependencyProperty,类似于上面的OnTextBlockTextChanged()属性,包括OnTextBlockTextChanged()方法。
  2. OnTextBlockTextChanged()方法中,在调用TrimText()之前将TrimText()设置为newValue
  3. TextBlockText属性绑定到View Model属性(在下面的XAML中称为SomeText
  4. TextBlock.Text属性绑定到XAML中的TextBlockTrimmer.TextBlockText属性:

       

如果我将TextBlockTrimmer.TextBlockTextTextBlock.Text绑定到SomeText它也会SomeText (但这样做SomeText我感到SomeText )。

下面是一个如何使用递归对数算法进行有效文本剪切的示例:

 private static string ClipTextToWidth( TextBlock reference, string text, double maxWidth) { var half = text.Substring(0, text.Length/2); if (half.Length > 0) { reference.Text = half; var actualWidth = reference.ActualWidth; if (actualWidth > maxWidth) { return ClipTextToWidth(reference, half, maxWidth); } return half + ClipTextToWidth( reference, text.Substring(half.Length, text.Length - half.Length), maxWidth - actualWidth); } return string.Empty; } 

假设您有一个名为textBlockTextBlock字段,并且您希望以给定的最大宽度剪切其中的文本,并附加省略号。 以下方法调用ClipTextToWidth来设置textBlock字段的文本:

 public void UpdateTextBlock(string text, double maxWidth) { if (text != null) { this.textBlock.Text = text; if (this.textBlock.ActualWidth > maxWidth) { this.textBlock.Text = "..."; var ellipsisWidth = this.textBlock.ActualWidth; this.textBlock.Text = "..." + ClipTextToWidth( this.textBlock, text, maxWidth - ellipsisWidth); } } else { this.textBlock.Text = string.Empty; } } 

希望有所帮助!

如果其他人像我一样偶然发现了这个问题,这里的另一个post有更好的答案(不记得):

在WPF标签中自动剪辑和附加点