Вопрос по wpf – Как я могу определить, обрезается ли мой текстовый блок?

37

Следующий текстовый блок переносится и обрезается, как и ожидалось. Элипсис "..." отображается, когда текст обрезан.

<code><TextBlock 
    MaxWidth="60" 
    MaxHeight="60" 
    Text="This is some long text which I would like to wrap."
    TextWrapping="Wrap" 
    TextTrimming="CharacterEllipsis" />
</code>

Я хотел бы отобразить всплывающую подсказку над текстом с полным текстом, но только если текст обрезан. Я не уверен, как надежно определить, является ли "..." показывается или нет.

Как определить, обрезается текст или нет?

Отлично поставленный вопрос - хороший. :) Mal Ross

Ваш Ответ

4   ответа
23

Так как ссылка в ответе Алек не работает, я нашел кэшированная копия ссылки с машины обратного хода. Вы не можете скачать код, указанный в статье, поэтому здесь представлена предварительно собранная версия кода. Была одна или две проблемы, с которыми я столкнулся, пытаясь заставить его работать, поэтому этот код немного отличается от кода в примерах в статье.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace TextBlockService
{
    //Based on the project from http://web.archive.org/web/20130316081653/http://tranxcoder.wordpress.com/2008/10/12/customizing-lookful-wpf-controls-take-2/
    public static class TextBlockService
    {
        static TextBlockService()
        {
            // Register for the SizeChanged event on all TextBlocks, even if the event was handled.
            EventManager.RegisterClassHandler(
                typeof(TextBlock),
                FrameworkElement.SizeChangedEvent,
                new SizeChangedEventHandler(OnTextBlockSizeChanged),
                true);
        }


        private static readonly DependencyPropertyKey IsTextTrimmedKey = DependencyProperty.RegisterAttachedReadOnly("IsTextTrimmed", 
            typeof(bool), 
            typeof(TextBlockService), 
            new PropertyMetadata(false));

        public static readonly DependencyProperty IsTextTrimmedProperty = IsTextTrimmedKey.DependencyProperty;

        [AttachedPropertyBrowsableForType(typeof(TextBlock))]
        public static Boolean GetIsTextTrimmed(TextBlock target)
        {
            return (Boolean)target.GetValue(IsTextTrimmedProperty);
        }


        public static readonly DependencyProperty AutomaticToolTipEnabledProperty = DependencyProperty.RegisterAttached(
            "AutomaticToolTipEnabled",
            typeof(bool),
            typeof(TextBlockService),
            new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));

        [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
        public static Boolean GetAutomaticToolTipEnabled(DependencyObject element)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            return (bool)element.GetValue(AutomaticToolTipEnabledProperty);
        }

        public static void SetAutomaticToolTipEnabled(DependencyObject element, bool value)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(AutomaticToolTipEnabledProperty, value);
        }

        private static void OnTextBlockSizeChanged(object sender, SizeChangedEventArgs e)
        {
            TriggerTextRecalculation(sender);
        }

        private static void TriggerTextRecalculation(object sender)
        {
            var textBlock = sender as TextBlock;
            if (null == textBlock)
            {
                return;
            }

            if (TextTrimming.None == textBlock.TextTrimming)
            {
                textBlock.SetValue(IsTextTrimmedKey, false);
            }
            else
            {
                //If this function is called before databinding has finished the tooltip will never show.
                //This invoke defers the calculation of the text trimming till after all current pending databinding
                //has completed.
                var isTextTrimmed = textBlock.Dispatcher.Invoke(() => CalculateIsTextTrimmed(textBlock), DispatcherPriority.DataBind);
                textBlock.SetValue(IsTextTrimmedKey, isTextTrimmed);
            }
        }

        private static bool CalculateIsTextTrimmed(TextBlock textBlock)
        {
            if (!textBlock.IsArrangeValid)
            {
                return GetIsTextTrimmed(textBlock);
            }

            Typeface typeface = new Typeface(
                textBlock.FontFamily,
                textBlock.FontStyle,
                textBlock.FontWeight,
                textBlock.FontStretch);

            // FormattedText is used to measure the whole width of the text held up by TextBlock container
            FormattedText formattedText = new FormattedText(
                textBlock.Text,
                System.Threading.Thread.CurrentThread.CurrentCulture,
                textBlock.FlowDirection,
                typeface,
                textBlock.FontSize,
                textBlock.Foreground);

            formattedText.MaxTextWidth = textBlock.ActualWidth;

            // When the maximum text width of the FormattedText instance is set to the actual
            // width of the textBlock, if the textBlock is being trimmed to fit then the formatted
            // text will report a larger height than the textBlock. Should work whether the
            // textBlock is single or multi-line.
            // The "formattedText.MinWidth > formattedText.MaxTextWidth" check detects if any 
            // single line is too long to fit within the text area, this can only happen if there is a 
            // long span of text with no spaces.
            return (formattedText.Height > textBlock.ActualHeight || formattedText.MinWidth > formattedText.MaxTextWidth);
        }

    }
}
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:tbs="clr-namespace:TextBlockService">
    <!--
    Rather than forcing *all* TextBlocks to adopt TextBlockService styles,
    using x:Key allows a more friendly opt-in model.
    -->

    <Style TargetType="TextBlock" x:Key="TextBlockService">
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="tbs:TextBlockService.AutomaticToolTipEnabled" Value="True" />
                    <Condition Property="tbs:TextBlockService.IsTextTrimmed" Value="True"/>
                </MultiTrigger.Conditions>

                <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text}" />
            </MultiTrigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>
Хорошие взлеты, использующие Wayback Machine. Я постоянно забываю, что это там. MarqueIV
Это уже «Opt-In», потому что вы должны включить его с помощьюAutomaticToolTipEnabled. (В ответ на код комментарияRather than forcing *all* TextBlocks to adopt TextBlockService styles, using x:Key allows a more friendly opt-in model.) Kelly Elton
@ KellyElton Я не хотел ставить ненужные триггеры на каждый TextBlock в окне. Вы можете просто применить его к каждому текстовому полю, если обнаружите, что оно не вызывает никаких накладных расходов. Приложение, для которого это было написано, было приложением для ввода данных с таблицей с редактируемыми ячейками, оно добавляло до нескольких сотен текстовых полей. Scott Chamberlain
9

поэтому я не уверен, что это то, что вам нужно, но посмотрите эту статью: Настройка «привлекательных» элементов управления WPF - Take 2. Это немного сложно, но кажется, что это тот же вопрос, что и вы. ОБНОВЛЕНИЕ: веб-сайт, кажется, ушел, но вы можете найти статью вархи.

Ссылка не работает: l33t
@ l33t Мне тоже нужна была ссылка, здесь копия страницы кэшируется Wayback Machine. Scott Chamberlain
Алгоритм, упомянутый в статье выше, это именно то, что я искал - спасибо! Andrew Jackson
Этот ответ действительно должен был изначально указать, как решить проблему, а не просто сбросить ссылку. sydan
Согласен с @sydan. Не просто ссылку. Бад С.О. поведение. Когда вы это сделаете, вы окажетесь в том же месте, где мы сейчас ... помеченный ответ, который на самом деле его не имеет. MarqueIV
4

если TextBlock является частью ListBoxItem DataTemplate. Я предлагаю другое решение:

public class MyTextBlock : System.Windows.Controls.TextBlock
{

    protected override void OnToolTipOpening(WinControls.ToolTipEventArgs e)
    {
        if (TextTrimming != TextTrimming.None)
        {
            e.Handled = !IsTextTrimmed(); 
        }
    }

    private bool IsTextTrimmed()
    {
        Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
        return ActualWidth < DesiredSize.Width;
    }
}

XAML:

  <MyTextBlock Text="{Binding Text}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Text}" />
Это хорошее чистое решение. Однако это не сработало для меня: мой TextBlock обрезается (я вижу многоточие), но мой DesiredSize.Width равен ActualWidth. thehelix
0

который показывает всплывающую подсказку только тогда, когда отображается не весь текст. Размер всплывающей подсказки будет изменен к содержанию (в отличие от всплывающей подсказки по умолчанию, которая останется однострочным с обрезанным текстом).

using System;
using System.Windows;
using System.Windows.Controls;

namespace Optimizers.App4Sales.LogViewer.Components
{
    public class CustomTextBlock : TextBlock
    {
        protected override void OnInitialized(EventArgs e)
        {
            // we want a tooltip that resizes to the contents -- a textblock with TextWrapping.Wrap will do that
            var toolTipTextBlock = new TextBlock();
            toolTipTextBlock.TextWrapping = TextWrapping.Wrap;
            // bind the tooltip text to the current textblock Text binding
            var binding = GetBindingExpression(TextProperty);
            if (binding != null)
            {
                toolTipTextBlock.SetBinding(TextProperty, binding.ParentBinding);
            }

            var toolTipPanel = new StackPanel();
            toolTipPanel.Children.Add(toolTipTextBlock);
            ToolTip = toolTipPanel;

            base.OnInitialized(e);
        }

        protected override void OnToolTipOpening(ToolTipEventArgs e)
        {
            if (TextTrimming != TextTrimming.None)
            {
                e.Handled = !IsTextTrimmed();
            }
        }

        private bool IsTextTrimmed()
        {
            Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            return ActualWidth < DesiredSize.Width;
        }
    }
}

XAML использование:

    <Window ...
        xmlns:components="clr-namespace:MyComponents"
     ... >

    <components:CustomTextBlock Text="{Binding Details}" TextTrimming="CharacterEllipsis" />

Похожие вопросы