Pergunta

Eu tenho um ListView WPF (GridView) eo modelo de célula contém uma TextBlock. Se eu adicionar: TextTrimming="CharacterEllipsis" TextWrapping="NoWrap" na TextBlock, uma elipse aparecerá no final da minha corda quando a coluna fica menor do que o comprimento da corda. O que eu preciso é ter as reticências no início da cadeia.

i. se eu tiver a Hello World! corda, eu gostaria ...lo World!, em vez de Hello W....

Todas as idéias?

Foi útil?

Solução

Você poderia tentar usar um ValueConverter (cf. IValueConverter interface de ) para alterar as cordas que devem ser exibidos na caixa de listagem si mesmo. Ou seja, na implementação do método Converter, você iria testar se as cordas são mais longos do que o espaço disponível, e então alterá-las para ... mais o lado direito da string.

Outras dicas

Eu estava enfrentando o mesmo problema e escreveu uma propriedade anexada para resolver este (ou a dizer, fornecer esse recurso). Doe meu código aqui:

Utilização

<controls:TextBlockTrimmer EllipsisPosition="Start">
    <TextBlock Text="Excuse me but can I be you for a while"
               TextTrimming="CharacterEllipsis" />
</controls:TextBlockTrimmer>

Não se esqueça de adicionar uma declaração de namespace em sua raiz Página / Janela / UserControl:

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

TextBlockTrimmer.EllipsisPosition pode ser Start, Middle (estilo mac) ou End. Certeza que você pode descobrir qual é qual de seus nomes.

CÓDIGO

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

Infelizmente, isso não é possível no WPF hoje, como você pode ver a partir da documentação .

(Eu costumava trabalho na Microsoft em WPF, este foi um recurso que infelizmente não se volta a fazer - não tenho certeza se ele está planejado para uma versão futura)

Eu implementei (copiados) o código TextBlockTrimmer acima e funcionou muito bem para o carregamento mas o TextBlock.Text não atualizar mais tarde, se ligado a uma propriedade Ver modelo que mudou. O que eu achei que trabalhava era

  1. Definir um DependencyProperty chamado TextBlockText em TextBlockTrimmer, semelhante à propriedade EllipsisPosition acima, incluindo um método OnTextBlockTextChanged().
  2. No método OnTextBlockTextChanged(), conjunto _originalText para newValue antes de chamar TrimText().
  3. Bind a propriedade TextBlockText à propriedade Ver Modelo (chamado SomeText no XAML abaixo)
  4. Bind a propriedade TextBlock.Text à propriedade TextBlockTrimmer.TextBlockText no XAML:

    <controls:TextBlockTrimmer EllipsisPosition="Middle" TextBlockText="{Binding SomeText, Mode=OneWay}"
        <TextBlock Text="{Binding TextBlockText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:TextBlockTrimmer}}}" HorizontalAlignment="Stretch"/>
    </controls:TextBlockTrimmer>
    

Ele também trabalhou se eu ligado tanto TextBlockTrimmer.TextBlockText e TextBlock.Text para SomeText (mas isso me incomoda).

Aqui está um exemplo de como fazer um recorte de texto eficiente com um algoritmo logarítmica recursiva:

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

Suponha que você tenha um campo TextBlock chamado textBlock, e que pretende cortar o texto nele em uma determinada largura máxima, com as reticências anexado. As seguintes chamadas de método ClipTextToWidth para definir o texto para o campo 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;
    }
}

Espero que ajude!

No caso de alguém tropeça mais neste pergunta como eu fiz, aqui é outro segmento com um (crédito não tomar) muito melhor resposta:

Auto clipe e Anexar pontos no rótulo WPF

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top