修剪文本时显示工具提示
Posted
技术标签:
【中文标题】修剪文本时显示工具提示【英文标题】:Show Tooltip when text is being trimmed 【发布时间】:2011-09-14 14:09:12 【问题描述】: <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 桌面快捷方式图标。
【问题讨论】:
【参考方案1】:在 Eyjafj 的基础上工作......不管有什么想法,我都得到了一个有效的、主要是声明性的解决方案,至少不需要自定义控件。要克服的第一个障碍是到达 TextBlock。由于 ToolTip 在可视化树之外呈现,因此您不能使用 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 或不想浪费资源来创建始终不可见的工具提示,则实际上说明了此方法here as an attached behavior。这是我的示例的完整代码:
转换器:
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>
【讨论】:
这很好用。已接受答案中的链接已失效,但我认为代码已在另一个问题中重现:***.com/questions/1041820/… 但是,这似乎有很多代码,这个转换器解决方案对我有用。 很高兴它有帮助!作为参考,如果我的答案中的链接消失了,它会链接到 Colin Eberhardt 的 AUTOMATICALLY SHOWING TOOLTIPS ON A TRIMMED TEXTBLOCK (SILVERLIGHT + WPF) 这种方法很不精确。对于某些文本,它运行良好,但如果有多达 8 像素的额外可用空间,其他文本已经获得工具提示。似乎取决于文本中的字符。 我发现了一个更好的修剪检测,并在我的回答中将其合并到了完整的类中。 @ygoe 我还没有注意到这个问题。您能否描述一下您发现的解决方案有什么问题,而您的补救措施(即为什么您的解决方案更好)?这将帮助读者决定使用哪个答案(并且可以帮助我避免遇到同样的问题;)【参考方案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"/>
【讨论】:
我喜欢这个解决方案,但稍作修改,通过调用 this.Measure(...) 并将 this.DesiredSize.Width 与 this.ActualWidth 进行比较来计算 IsTextTrimmed【参考方案3】:基于此页面上的想法和来自another answer 的额外算法更正,我制作了这个非常便携的类,可以非常轻松地使用。其目的是在文本被修剪时启用修剪并在 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://***.com/a/21863054
// Source 2: https://***.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();
【讨论】:
这个答案非常适合重复使用,除了您实际上是在设置工具提示的样式(例如 Background = Brushes.White)。为什么答案中会包含这些内容? @Skrymsli 我不知道为什么会这样。可能是工具提示直接显示在 TextBlock 文本上,而不是在某个偏移处或鼠标光标周围?因此,考虑到大多数 TextBlock 都是黑白的,这应该会导致 ToolTip 看起来就像 TextBlock 本身一样。否则,您可以随意设置 ToolTip 的样式,甚至尝试适应 TextBlock 的实际外观。此外,不幸的是,随着时间的推移,随着新版本的 Windows 和随附的 WPF 主题,确切的填充往往会发生变化。【参考方案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://***.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】:我认为您可以创建一个转换器,将textblock
的ActualWidth
与DesiredSize.Width
进行比较,然后返回Visibility
。
【讨论】:
【参考方案6】:发布了带有附加属性here 的替代答案,我认为这比使用转换器或派生的 TextBlock 控件更好。
【讨论】:
【参考方案7】:我使用了来自@pogosoma 的this,但使用了来自@snicker 的CalculateIsTextTrimmed
函数,这是完美的
private static void SetTooltipBasedOnTrimmingState(TextBlock tb)
Typeface typeface = new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch);
FormattedText formattedText = new FormattedText(tb.Text, System.Threading.Thread.CurrentThread.CurrentCulture, tb.FlowDirection, typeface, tb.FontSize, tb.Foreground)
MaxTextWidth = tb.ActualWidth ;
bool isTextTrimmed = (formattedText.Height > tb.ActualHeight || formattedText.MinWidth > formattedText.MaxTextWidth);
ToolTipService.SetToolTip(tb, isTextTrimmed ? tb.ToolTip : null);
【讨论】:
以上是关于修剪文本时显示工具提示的主要内容,如果未能解决你的问题,请参考以下文章