C# 修剪文本时显示工具提示

C# 修剪文本时显示工具提示,c#,wpf,xaml,tooltip,texttrimming,C#,Wpf,Xaml,Tooltip,Texttrimming,如何仅在修剪文本时显示工具提示?像windows desktp快捷方式图标。我想你可以创建一个转换器,将文本块的实际宽度与它的所需大小.宽度进行比较,并返回可见性在Eyjafj上工作……不管是什么想法,我得出了一个工作状态,主要是声明性的解决方案,至少不需要自定义控件。要克服的第一个障碍是获取文本块。由于工具提示在可视化树之外呈现,因此不能使用RelativeSource绑定或ElementName获取TextBlock。幸运的是,ToolTip类通过PlacementTarget属性提供了对其

如何仅在修剪文本时显示
工具提示
?像windows desktp快捷方式图标。

我想你可以创建一个转换器,将
文本块的
实际宽度
与它的
所需大小.宽度
进行比较,并返回
可见性
在Eyjafj上工作……不管是什么想法,我得出了一个工作状态,主要是声明性的解决方案,至少不需要自定义控件。要克服的第一个障碍是获取文本块。由于工具提示在可视化树之外呈现,因此不能使用RelativeSource绑定或ElementName获取TextBlock。幸运的是,ToolTip类通过PlacementTarget属性提供了对其相关元素的引用。因此,可以将工具提示的可见性属性绑定到工具提示本身,并使用其PlacementTarget属性访问TextBlock的属性:

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

下一步是使用转换器查看绑定到的文本块,以确定工具提示是否可见。您可以使用实际宽度和所需大小来执行此操作。实际宽度正是它听起来的样子;文本块在屏幕上呈现的宽度。DesiredSize是文本块希望的宽度。唯一的问题是,DesiredSize似乎考虑了文本修剪,并没有给出完整、未修剪文本的宽度。为了解决这个问题,我们可以重新调用传递Double.Positive infinity的度量方法,实际上是询问如果文本块的宽度不受约束,它的宽度会有多宽。这将更新DesiredSize属性,然后我们可以进行比较:

<ToolTip Visibility="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget, Converter={StaticResource trimmedVisibilityConverter}}">
textBlock.Measure(新大小(Double.PositiveInfinity,Double.PositiveInfinity));
如果(((FrameworkElement)值).ActualWidth<((FrameworkElement)值).DesiredSize.Width)
返回可见性。可见;
如果您想将其自动应用于文本块,或者不想浪费资源来创建始终不可见的工具提示,那么实际上可以演示这种方法。下面是我的示例的完整代码:

转换器:

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

if (((FrameworkElement)value).ActualWidth < ((FrameworkElement)value).DesiredSize.Width)
    return Visibility.Visible;
公共类TrimmedTextBlockVisibilityConverter:IValueConverter
{
公共对象转换(对象值、类型targetType、对象参数、System.Globalization.CultureInfo区域性)
{
if(value==null)返回Visibility.Collapsed;
FrameworkElement textBlock=(FrameworkElement)值;
textBlock.Measure(新系统.Windows.Size(Double.PositiveInfinity,Double.PositiveInfinity));
如果(((FrameworkElement)值).ActualWidth<((FrameworkElement)值).DesiredSize.Width)
返回可见性。可见;
其他的
返回可见性。折叠;
}
公共对象转换回(对象值、类型targetType、对象参数、System.Globalization.CultureInfo区域性)
{
抛出新的NotImplementedException();
}
}
XAML:

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

....

我找到了扩展TextBlock并比较文本长度以确定是否显示工具提示的最简单解决方案,即

<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>
然后只需在xaml中使用此自定义文本块,如下所示:

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


发布了一个带有附加属性的备选答案,我认为这比使用转换器或派生的TextBlock控件更好。

基于本页上的想法,并结合我的其他算法更正,我制作了这个非常可移植的类,可以非常容易地使用。它的目的是启用修剪,并在修剪文本时在文本块上显示工具提示,就像许多应用程序所知道的那样

修剪检测在我的应用中被证明是精确的。显示修剪省略号时,工具提示将准确显示

XAML使用 全班
使用系统;
利用制度全球化;
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Data;
使用System.Windows.Media;
命名空间未分类.UI
{
/// 
///修剪文本块时,在其上显示工具提示。
/// 
公共类TextBlockAutoToolTip
{
/// 
///已启用的附加属性。
/// 
公共静态只读DependencyProperty EnabledProperty=DependencyProperty.RegisterAttached(
“启用”,
类型(bool),
类型(TextBlockAutoToolTip),
新的FrameworkPropertyMetadata(新的PropertyChangedCallback(OnAutoToolTipeEnabledChanged));
/// 
///设置TextBlock控件上已启用的附加属性。
/// 
///文本块控件。
///价值。
公共静态void SetEnabled(DependencyObject DependencyObject,bool enabled)
{
dependencyObject.SetValue(EnabledProperty,enabled);
}
私有静态只读TrimmedTextBlockVisibilityConverter ttbvc=新TrimmedTextBlockVisibilityConverter();
AutoToolTipeEnabledChanged上的私有静态无效(DependencyObject DependencyObject,DependencyPropertyChangedEventArgs args)
{
TextBlock TextBlock=作为TextBlock的dependencyObject;
if(textBlock!=null)
{
bool enabled=(bool)args.NewValue;
如果(已启用)
{
变量工具提示=新工具提示
{
Placement=System.Windows.Controls.Primitives.PlacementMode.Relative,
垂直偏移=-3,
水平偏移=-5,
衬垫=新厚度(4,2,4,2),
背景=画笔。白色
};
工具提示.SetBinding(UIElement.VisibilityProperty,new System.Windows.Data.Binding
{
RelativeSource=新系统.Windo
<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"/>
<!-- 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: 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();
            }
        }
    }
}
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);
    }
}