Ellipsis au début de la chaîne dans WPF ListView

J’ai un WPF ListView ( GridView ) et le modèle de cellule contient un TextBlock . Si j’ajoute: TextTrimming="CharacterEllipsis" TextWrapping="NoWrap" sur le TextBlock , un sharepoint suspension apparaîtra à la fin de ma chaîne lorsque la colonne devient inférieure à la longueur de la chaîne. Ce dont j’ai besoin, c’est d’avoir des points de suspension au début de la chaîne.

C’est à dire si j’ai la chaîne Hello World! , Je voudrais ...lo World! , au lieu de Hello W...

Des idées?

    Vous pouvez essayer d’utiliser un ValueConverter (cf. interface IValueConverter ) pour changer vous-même les chaînes à afficher dans la liste. En d’autres termes, lors de l’implémentation de la méthode Convert, vous devez vérifier si les chaînes sont plus longues que l’espace disponible, puis les remplacer par … plus le côté droit de la chaîne.

    Je faisais face au même problème et ai écrit une propriété attachée pour résoudre ceci (ou pour dire, fournir cette fonctionnalité). Donner mon code ici:

    USAGE

        

    N’oubliez pas d’append une déclaration d’espace de noms à la racine de votre page / fenêtre / utilisateur:

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

    TextBlockTrimmer.EllipsisPosition peut être Start , Middle (style mac) ou End . Assez sûr que vous pouvez savoir qui est lequel de leurs noms.

    CODE

    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 ssortingng 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 ssortingng _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 MeasureSsortingng(TextBlock textBlock, ssortingng 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 SsortingngBuilder(); 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.Subssortingng(0, textLength); var secondHalf = _originalText.Subssortingng(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.ToSsortingng(); } } private static void TrimText(TextBlock textBlock, ssortingng text, double size, ICollection segments, double epsilon, bool reversed) { while (true) { if (text.Length == 1) { var textSize = TextBlockTrimmer.MeasureSsortingng(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.Subssortingng(0, halfLength) : text.Subssortingng(halfLength); text = secondHalf; size = remainingSize; continue; } break; } } } } 

    Malheureusement, cela n’est pas possible dans WPF aujourd’hui, comme vous pouvez le constater dans la documentation .

    (J’avais l’habitude de travailler chez Microsoft sur WPF, c’était une fonctionnalité que nous n’avions malheureusement pas encore trouvée – je ne sais pas si c’est prévu pour une future version)

    J’ai implémenté (copié) le code TextBlockTrimmer ci-dessus et cela fonctionnait très bien pour le chargement, mais TextBlock.Text ne serait pas mis à jour par la suite, s’il était lié à une propriété View Model modifiée. Ce que j’ai trouvé qui a fonctionné était de

    1. Définissez un object DependencyProperty appelé TextBlockText dans TextBlockTrimmer , similaire à la propriété EllipsisPosition ci-dessus, y compris une méthode OnTextBlockTextChanged() .
    2. Dans la méthode OnTextBlockTextChanged() , définissez _originalText sur newValue avant d’appeler TrimText() .
    3. Liez la propriété TextBlockText propriété View Model (appelée SomeText dans le SomeText XAML ci-dessous).
    4. Liez la propriété TextBlock.Text à la propriété TextBlockTrimmer.TextBlockText dans le TextBlockTrimmer.TextBlockText XAML:

         

    Cela fonctionnait aussi si je TextBlockTrimmer.TextBlockText deux TextBlockTrimmer.TextBlockText et TextBlock.Text à SomeText (mais cela me SomeText ).

    Voici un exemple de découpage de texte efficace avec un algorithme logarithmique récursif:

     private static ssortingng ClipTextToWidth( TextBlock reference, ssortingng text, double maxWidth) { var half = text.Subssortingng(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.Subssortingng(half.Length, text.Length - half.Length), maxWidth - actualWidth); } return ssortingng.Empty; } 

    Supposons que vous ayez un champ TextBlock nommé textBlock et que vous souhaitiez textBlock le texte dans une largeur maximale donnée, avec les points de suspension ajoutés. La méthode suivante appelle ClipTextToWidth pour définir le texte du champ textBlock :

     public void UpdateTextBlock(ssortingng 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 = ssortingng.Empty; } } 

    J’espère que cela pourra aider!

    Si quelqu’un d’autre tombe comme moi sur cette question, voici un autre fil avec une bien meilleure réponse (ne prenant pas de crédit):

    Clip automatique et append des points dans l’étiquette WPF