Подтвердить что ты не робот

Показывать подсказку, когда текст обрезается

  <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.

4b9b3361

Ответ 1

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

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

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

Ответ 2

Я нашел простейшее решение для расширения 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"/>

Ответ 3

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

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

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

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

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

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: /questions/166954/how-can-i-determine-if-my-textblock-text-is-being-trimmed/950458#950458

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

Ответ 4

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

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: https://stackoverflow.com/questions/1041820/how-can-i-determine-if-my-textblock-text-is-being-trimmed
    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);
    }
}

Ответ 5

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

Ответ 6

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