修剪文本时显示工具提示

问题描述

|
  <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>
仅在修剪文本时如何显示ѭ1?就像Windows desktp快捷方式图标一样。     

解决方法

        不管Eyjafj的想法是什么,我想到了一个可行的,主要是声明性的解决方案,至少不需要自定义控件。要克服的第一个障碍是获得TextBlock。因为工具提示是在可视树之外渲染的,所以您不能使用RelativeSource绑定或ElementName来获取TextBlock。幸运的是,ToolTip类通过PlacementTarget属性提供了对其相关元素的引用。因此,您可以将ToolTip \的Visibility属性绑定到ToolTip本身,并使用其PlacementTarget属性访问TextBlock的属性:
<ToolTip Visibility=\"{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget,Converter={StaticResource trimmedVisibilityConverter}}\">
下一步是使用转换器查看我们必须确定的TextBlock,以确定ToolTip是否可见。您可以使用ActualWidth和DesiredSize做到这一点。 ActualWidth确实就是它的模样;您的TextBlock呈现在屏幕上的宽度。 DesiredSize是您的TextBlock希望的宽度。唯一的问题是,DesiredSize似乎考虑了TextTrimming,但没有给您完整的,未修剪的文本的宽度。为了解决这个问题,我们可以调用传递Double.Positive infinity的Measure方法,以有效地询问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,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},Converter={StaticResource trimmedVisibilityConverter}}\">
            <ToolTip.Content>
                <TextBlock Text=\"{Binding SomeTextProperty}\"/>
            </ToolTip.Content>
        </ToolTip>
    </TextBlock.ToolTip>
</TextBlock>
    ,        基于此页面上的想法,并通过另一个答案的其他算法更正,我使此类非常易于移植,可以非常轻松地使用。它的目的是启用修剪功能,并在修剪文本时在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,Path = new PropertyPath(\"PlacementTarget.Text\")
                    });
                    toolTip.SetBinding(Control.ForegroundProperty,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,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,CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }
}
    ,        我找到了扩展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,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\"/>
    ,        行为是爱,行为是生活。
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.FontStretch);

        // FormattedText is used to measure the whole width of the text held up by TextBlock container
        FormattedText formattedText = new FormattedText(
            textBlock.Text,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,typeof (T));
        desc.RemoveValueChanged(obj,handler);
    }
}
    ,        我认为您可以创建一个转换器,以比较ѭ15的
ActualWidth
DesiredSize.Width
,然后返回
Visibility
。     ,        在此处发布了一个带有附加属性的替代答案,我认为这比使用转换器或派生的TextBlock控件更好。     

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...