Приложение Windows определяет, обрезан ли TextBlock

У меня есть GridItem с фиксированной высотой/шириной.

Он содержит текстовый блок с максимальным набором строк.

Как я могу определить, обрезан ли этот текст? Я хочу добавить специальный функционал, если он будет обрезан.


person Frank Sposaro    schedule 29.08.2014    source источник


Ответы (2)


Старый способ — когда для TextWrapping установлено значение None

Чтобы узнать, обрезан ли TextBlock, мы можем подписаться на его событие SizeChanged и сравнить его ActualWidth с указанным вами MaxWidth. Чтобы получить справа ActualWidth от TextBlock, нам нужно оставить для TextTrimming значение по умолчанию (т. е. TextTrimming.None) и настроить его на обрезание, как только ширина превысит ширину.

Новый способ — когда TextWrapping установлен на Wrap

Теперь, когда я знаю, поскольку TextWrapping установлено на Wrap и предполагается, что VirticalAlignment не указано (по умолчанию Stretch), Width всегда будет оставаться неизменным. Нам нужно только отслеживать событие SizeChanged, когда фактическая высота TextBlock превышает высоту его родителя.

Давайте используем Behavior для инкапсуляции всей вышеописанной логики. Здесь следует упомянуть, что вспомогательный класс static с набором присоединенных свойств или новый элемент управления, унаследованный от TextBlock, может делать то же самое; но, будучи большим поклонником Blend, я предпочитаю использовать Behaviors, когда это возможно.

Поведение

public class TextBlockAutoTrimBehavior : DependencyObject, IBehavior
{
    public bool IsTrimmed
    {
        get { return (bool)GetValue(IsTrimmedProperty); }
        set { SetValue(IsTrimmedProperty, value); }
    }

    public static readonly DependencyProperty IsTrimmedProperty =
        DependencyProperty.Register("IsTrimmed", typeof(bool), typeof(TextBlockAutoTrimBehavior), new PropertyMetadata(false));

    public DependencyObject AssociatedObject { get; set; }

    public void Attach(DependencyObject associatedObject)
    {
        this.AssociatedObject = associatedObject;
        var textBlock = (TextBlock)this.AssociatedObject;

        // subscribe to the SizeChanged event so we will know when the Width of the TextBlock goes over the MaxWidth
        textBlock.SizeChanged += TextBlock_SizeChanged;
    }

    private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        // ignore the first time height change
        if (e.PreviousSize.Height != 0)
        {
            var textBlock = (TextBlock)sender;

            // notify the IsTrimmed dp so your viewmodel property will be notified via data binding
            this.IsTrimmed = true;
            // unsubscribe the event as we don't need it anymore
            textBlock.SizeChanged -= TextBlock_SizeChanged;

            // then we trim the TextBlock
            textBlock.TextTrimming = TextTrimming.WordEllipsis;
        }
    }

    public void Detach()
    {
        var textBlock = (TextBlock)this.AssociatedObject;
        textBlock.SizeChanged += TextBlock_SizeChanged;
    }
}

XAML

<Grid HorizontalAlignment="Center" Height="73" VerticalAlignment="Center" Width="200" Background="#FFD2A6A6" Margin="628,329,538,366">
    <TextBlock x:Name="MyTextBlock" TextWrapping="Wrap" Text="test" FontSize="29.333">
        <Interactivity:Interaction.Behaviors>
            <local:TextBlockAutoTrimBehavior IsTrimmed="{Binding IsTrimmedInVm}" />
        </Interactivity:Interaction.Behaviors>
    </TextBlock>
</Grid>

Обратите внимание, что Behavior предоставляет свойство зависимости IsTrimmed, вы можете привязать его данные к свойству в вашей модели представления (т.е. IsTrimmedInVm в данном случае).

P.S. В WinRT нет функции FormattedText, иначе реализация могла бы немного отличаться.

person Justin XL    schedule 08.09.2014
comment
Ширина также учитывает высоту? Это означает, что если мой блок шириной 100 имеет высоту 2 строки, ширина этого текста теперь равна 200? - person Frank Sposaro; 08.09.2014
comment
О, если это так, то все еще проще. На самом деле нам больше не нужен MaxWidth. Поскольку TextBlock растягивается по горизонтали и вертикали, его ширина останется прежней. Высота изменится только тогда, когда она превысит родительскую высоту. Итак, все, что нам нужно сделать, это прослушать событие SizeChanged и сделать что-то, когда высота превысит допустимый предел. Я обновил behavior, а также добавил TextWrapping в xaml. Теперь он должен работать на любом количестве строк. - person Justin XL; 09.09.2014

В итоге мы сделали статическую функцию

    // Ensure block does not have MAXLINES or text trimming set prior to checking
    public static bool IsTruncated(TextBlock block, int maxLines)
    {
        if (block == null)
        {
            throw new ArgumentNullException("block");
        }

        //the cut-off height is the height at which text will be cut off in the UI
        var cutOffHeight = maxLines * block.LineHeight;

        //determine whether the actual height of the TextBlock is greater than the cut-off height
        return block.ActualHeight > cutOffHeight;
    }

Хитрость заключается в том, чтобы убедиться, что Maxlines и обрезка текста НЕ установлены в текстовом блоке до запуска этой функции. После того, как эта функция вернется, то есть когда будут установлены Maxlines. В моем случае я просто сохранил возвращенное логическое значение в содержащем объекте, поэтому знал, что оно длиннее. Затем я устанавливаю maxlines и еще одну кнопку, чтобы увидеть расширенный контент на основе этого логического значения.

person Frank Sposaro    schedule 11.09.2014
comment
Хотите объяснить, почему мое решение не работает? ИМХО, моя лучше. - person Justin XL; 12.09.2014
comment
Я не говорил, что твой не работает. Я только что опубликовал решение, с которым мы действительно пошли. Помимо того, что наше решение короче, мы избегаем привязки данных, которая была основной проблемой. Я ценю ваш ответ, и мы проголосовали за него несколько раз. - person Frank Sposaro; 12.09.2014