Показать подсказку, когда текст обрезается - PullRequest
17 голосов
/ 14 июня 2011
  <TextBlock Width="100" Text="The quick brown fox jumps over the lazy dog" TextTrimming="WordEllipsis">
     <TextBlock.ToolTip>
        <ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
           <TextBlock Text="{Binding Text}"/>
        </ToolTip>
     </TextBlock.ToolTip>
  </TextBlock>

Как я могу показать ToolTip только когда текст обрезан?Как и ярлыки Windows Desktp.

Ответы [ 6 ]

15 голосов
/ 18 февраля 2014

Отработка Eyjafj ... независимо от идеи, я пришел к рабочему, в основном декларативному решению, которое, по крайней мере, не требует специального элемента управления.Первое препятствие, которое нужно преодолеть, это добраться до TextBlock.Поскольку подсказка отображается за пределами визуального дерева, вы не можете использовать привязку RelativeSource или ElementName для доступа к TextBlock.К счастью, класс ToolTip предоставляет ссылку на связанный элемент через свойство PlacementTarget.Таким образом, вы можете привязать свойство Visibility ToolTip к самой ToolTip и использовать его свойство PlacementTarget для доступа к свойствам TextBlock:

<ToolTip Visibility="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget, Converter={StaticResource trimmedVisibilityConverter}}">

Следующий шаг - использование конвертера для просмотра TextBlock, с которым мы связанычтобы определить, должна ли подсказка быть видимой или нет.Вы можете сделать это, используя ActualWidth и DesiredSize.ActualWidth - именно то, на что это похоже;ширина вашего TextBlock была отображена на экране.DesiredSize - это ширина, которую предпочитает ваш TextBlock.Единственная проблема заключается в том, что DesiredSize, похоже, принимает во внимание TextTrimming и не дает ширину полного необрезанного текста.Чтобы решить эту проблему, мы можем повторно вызвать метод Measure, передав бесконечность Double.Positive, чтобы по сути спросить, насколько широким будет TextBlock, если бы его ширина не была ограничена.Это обновляет свойство DesiredSize, а затем мы можем сделать сравнение:

textBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

if (((FrameworkElement)value).ActualWidth < ((FrameworkElement)value).DesiredSize.Width)
    return Visibility.Visible;

Этот подход фактически иллюстрируется здесь как прикрепленное поведение , если вы хотите применить его автоматически к TextBlocks или неЯ не хочу тратить ресурсы на создание всплывающих подсказок, которые всегда будут невидимы.Вот полный код для моего примера:

Конвертер:

public class TrimmedTextBlockVisibilityConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) return Visibility.Collapsed;

        FrameworkElement textBlock = (FrameworkElement)value;

        textBlock.Measure(new System.Windows.Size(Double.PositiveInfinity, Double.PositiveInfinity));

        if (((FrameworkElement)value).ActualWidth < ((FrameworkElement)value).DesiredSize.Width)
            return Visibility.Visible;
        else
            return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

XAML:

<UserControl.Resources>
    <local:TrimmedTextBlockVisibilityConverter x:Key="trimmedVisibilityConverter" />
</UserControl.Resources>

....

<TextBlock TextTrimming="CharacterEllipsis" Text="{Binding SomeTextProperty}">
    <TextBlock.ToolTip>
        <ToolTip Visibility="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget, Converter={StaticResource trimmedVisibilityConverter}}">
            <ToolTip.Content>
                <TextBlock Text="{Binding SomeTextProperty}"/>
            </ToolTip.Content>
        </ToolTip>
    </TextBlock.ToolTip>
</TextBlock>
8 голосов
/ 30 августа 2016

Основываясь на идеях на этой странице и с дополнительными алгоритмическими исправлениями из другой ответ Я создал этот очень переносимый класс, который можно использовать очень легко. Его цель - включить обрезку и показать всплывающую подсказку поверх TextBlock, когда текст обрезается, как это известно из многих приложений.

Обнаружение обрезки оказалось точным в моем приложении. Подсказка инструмента отображается точно тогда, когда отображается обрезной многоточие.

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

<!-- xmlns:ui="clr-namespace:Unclassified.UI" -->
<TextBlock Text="Demo" ui:TextBlockAutoToolTip.Enabled="True"/>

C # использование

var textBlock = new TextBlock { Text = "Demo" };
TextBlockAutoToolTip.SetEnabled(textBlock, true);

Полный класс

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace Unclassified.UI
{
    /// <summary>
    /// Shows a ToolTip over a TextBlock when its text is trimmed.
    /// </summary>
    public class TextBlockAutoToolTip
    {
        /// <summary>
        /// The Enabled attached property.
        /// </summary>
        public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
            "Enabled",
            typeof(bool),
            typeof(TextBlockAutoToolTip),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnAutoToolTipEnabledChanged)));

        /// <summary>
        /// Sets the Enabled attached property on a TextBlock control.
        /// </summary>
        /// <param name="dependencyObject">The TextBlock control.</param>
        /// <param name="enabled">The value.</param>
        public static void SetEnabled(DependencyObject dependencyObject, bool enabled)
        {
            dependencyObject.SetValue(EnabledProperty, enabled);
        }

        private static readonly TrimmedTextBlockVisibilityConverter ttbvc = new TrimmedTextBlockVisibilityConverter();

        private static void OnAutoToolTipEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
        {
            TextBlock textBlock = dependencyObject as TextBlock;
            if (textBlock != null)
            {
                bool enabled = (bool)args.NewValue;
                if (enabled)
                {
                    var toolTip = new ToolTip
                    {
                        Placement = System.Windows.Controls.Primitives.PlacementMode.Relative,
                        VerticalOffset = -3,
                        HorizontalOffset = -5,
                        Padding = new Thickness(4, 2, 4, 2),
                        Background = Brushes.White
                    };
                    toolTip.SetBinding(UIElement.VisibilityProperty, new System.Windows.Data.Binding
                    {
                        RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
                        Path = new PropertyPath("PlacementTarget"),
                        Converter = ttbvc
                    });
                    toolTip.SetBinding(ContentControl.ContentProperty, new System.Windows.Data.Binding
                    {
                        RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
                        Path = new PropertyPath("PlacementTarget.Text")
                    });
                    toolTip.SetBinding(Control.ForegroundProperty, new System.Windows.Data.Binding
                    {
                        RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
                        Path = new PropertyPath("PlacementTarget.Foreground")
                    });
                    textBlock.ToolTip = toolTip;
                    textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
                }
            }
        }

        private class TrimmedTextBlockVisibilityConverter : IValueConverter
        {
            // Source 1: https://stackoverflow.com/a/21863054
            // Source 2: https://stackoverflow.com/a/25436070

            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var textBlock = value as TextBlock;
                if (textBlock == null)
                    return Visibility.Collapsed;

                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,
                    VisualTreeHelper.GetDpi(textBlock).PixelsPerDip);

                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 width 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.
                bool isTrimmed = formattedText.Height > textBlock.ActualHeight ||
                    formattedText.MinWidth > formattedText.MaxTextWidth;

                return isTrimmed ? Visibility.Visible : Visibility.Collapsed;
            }

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }
}
6 голосов
/ 06 мая 2015

Я нашел самое простое решение для расширения TextBlock и сравнения длин текста, чтобы определить, показывать ли всплывающую подсказку, т. Е.

public class ToolTipTextBlock : TextBlock
   {
      protected override void OnToolTipOpening(ToolTipEventArgs e)
      {
         if (TextTrimming != TextTrimming.None)
         {
           e.Handled = !IsTextTrimmed();
         }
      }

      private bool IsTextTrimmed()
      {
         var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
         var formattedText = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection, typeface, FontSize, Foreground);
         return formattedText.Width > ActualWidth;
      }
   }

Затем просто используйте этот пользовательский текстовый блок в xaml следующим образом:

<local:ToolTipTextBlock Text="This is some text that I'd like to show tooltip for!"
    TextTrimming="CharacterEllipsis"
    ToolTip="{Binding Text,RelativeSource={RelativeSource Self}}"
    MaxWidth="10"/>
5 голосов
/ 21 ноября 2016

Поведение - это любовь, поведение - это жизнь.

public class TextBlockAutoToolTipBehavior : Behavior<TextBlock>
{
    private ToolTip _toolTip;

    protected override void OnAttached()
    {
        base.OnAttached();
        _toolTip = new ToolTip
        {
            Placement = PlacementMode.Relative,
            VerticalOffset = 0,
            HorizontalOffset = 0
        };

        ToolTipService.SetShowDuration(_toolTip, int.MaxValue);

        _toolTip.SetBinding(ContentControl.ContentProperty, new Binding
        {
            Path = new PropertyPath("Text"),
            Source = AssociatedObject
        });

        AssociatedObject.TextTrimming = TextTrimming.CharacterEllipsis;
        AssociatedObject.AddValueChanged(TextBlock.TextProperty, TextBlockOnTextChanged);
        AssociatedObject.SizeChanged += AssociatedObjectOnSizeChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.RemoveValueChanged(TextBlock.TextProperty, TextBlockOnTextChanged);
        AssociatedObject.SizeChanged -= AssociatedObjectOnSizeChanged;
    }

    private void AssociatedObjectOnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
    {
        CheckToolTipVisibility();
    }

    private void TextBlockOnTextChanged(object sender, EventArgs eventArgs)
    {
        CheckToolTipVisibility();
    }

    private void CheckToolTipVisibility()
    {
        if (AssociatedObject.ActualWidth == 0)
            Dispatcher.BeginInvoke(
                new Action(
                    () => AssociatedObject.ToolTip = CalculateIsTextTrimmed(AssociatedObject) ? _toolTip : null),
                DispatcherPriority.Loaded);
        else
            AssociatedObject.ToolTip = CalculateIsTextTrimmed(AssociatedObject) ? _toolTip : null;
    }

    //Source: /1212205/kak-ya-mogu-opredelit-obrezaetsya-li-moi-tekstovyi-blok
    private static bool CalculateIsTextTrimmed(TextBlock 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) {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 width 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);
    }
}

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

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:behavior="clr-namespace:MyWpfApplication.Behavior"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <TextBlock Text="{Binding Text}">
        <i:Interaction.Behaviors>
            <behavior:TextBlockAutoToolTipBehavior />
        </i:Interaction.Behaviors>
    </TextBlock>
</Window>

Необходимые методы расширения:

public static class UITools
{
    public static void AddValueChanged<T>(this T obj, DependencyProperty property, EventHandler handler)
        where T : DependencyObject
    {
        var desc = DependencyPropertyDescriptor.FromProperty(property, typeof (T));
        desc.AddValueChanged(obj, handler);
    }

    public static void RemoveValueChanged<T>(this T obj, DependencyProperty property, EventHandler handler)
        where T : DependencyObject
    {
        var desc = DependencyPropertyDescriptor.FromProperty(property, typeof (T));
        desc.RemoveValueChanged(obj, handler);
    }
}
2 голосов
/ 14 июня 2011

Я думаю, что вы можете создать конвертер, который сравнивает ActualWidth из textblock и DesiredSize.Width и возвращает Visibility.

1 голос
/ 18 сентября 2015

Опубликован альтернативный ответ с прикрепленным свойством здесь , что, на мой взгляд, лучше, чем использование конвертера или производного элемента управления TextBlock.

...