Here's my two cents based on pogosama answer.
When a TextBlock is reused, I had to detect text changes because size changes was not sufficient.
Also, this solution handle a templated version of the ToolTip.
Here's the key : ToolTipService.SetIsEnabled(tb, isTextTrimmed);
OnTextBlockSizeChanged
is used in both cases because OnToolTipTextChanged
is called before, therefore FormattedText
is not computed yet.
public static class TextBlockUtils
{
/// <summary>
/// Used by the simple version
/// </summary>
public static readonly DependencyProperty ToolTipTextProperty =
DependencyProperty.RegisterAttached("ToolTipText", typeof(string), typeof(TextBlockUtils), new PropertyMetadata(null, OnToolTipTextChanged));
public static string GetToolTipText(DependencyObject obj) => (string)obj.GetValue(ToolTipTextProperty);
public static void SetToolTipText(DependencyObject obj, string value) => obj.SetValue(ToolTipTextProperty, value);
public static readonly DependencyProperty HasSubscribedProperty =
DependencyProperty.RegisterAttached("HasSubscribed", typeof(bool), typeof(TextBlockUtils), new PropertyMetadata(false, null));
public static bool GetHasSubscribed(DependencyObject d) => (bool)d.GetValue(HasSubscribedProperty);
public static void SetHasSubscribed(DependencyObject d, bool value) => d.SetValue(HasSubscribedProperty, value);
/// <summary>
/// Used by the templated version
/// </summary>
public static readonly DependencyProperty AutoToolTipProperty =
DependencyProperty.RegisterAttached("AutoToolTip", typeof(bool), typeof(TextBlockUtils), new PropertyMetadata(false, OnAutoToolTipPropertyChanged));
public static bool GetAutoToolTip(DependencyObject d) => (bool)d.GetValue(AutoToolTipProperty);
public static void SetAutoToolTip(DependencyObject d, bool value) => d.SetValue(AutoToolTipProperty, value);
private static void OnToolTipTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock tb = d as TextBlock;
if (!GetHasSubscribed(tb))//anti multi subscribe
{
tb.SizeChanged -= OnTextBlockSizeChanged;
tb.SizeChanged += OnTextBlockSizeChanged;
SetHasSubscribed(tb, true);
}
SetToolTipBasedOnTrimmingState(tb);
}
private static void OnAutoToolTipPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tb = d as TextBlock;
bool newValue = (bool)e.NewValue;
if (newValue)
{
SetToolTipBasedOnTrimmingState(tb);
tb.SizeChanged += OnTextBlockSizeChanged;
}
else
{
tb.SizeChanged -= OnTextBlockSizeChanged;
}
}
private static void OnTextBlockSizeChanged(object sender, SizeChangedEventArgs e)
{
var tb = sender as TextBlock;
SetToolTipBasedOnTrimmingState(tb);
}
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.SetIsEnabled(tb, isTextTrimmed);//because ToolTipService.ShowOnDisabledProperty = false, this works
object templatedToolTip = tb.FindName("TemplatedToolTip");
if (templatedToolTip != null && templatedToolTip is ToolTip)
{
ToolTip toolTip = templatedToolTip as ToolTip;
tb.ToolTip = toolTip;
}
else
tb.ToolTip = GetToolTipText(tb);
}
}
Simple version
<TextBlock Text={Binding MyText} utils:TextBlockUtils.ToolTipText="{Binding Text, RelativeSource={RelativeSource Self}, Mode=OneWay}" TextTrimming="WordEllipsis">
or
<TextBlock Text={Binding MyText} utils:TextBlockUtils.ToolTipText="{Binding Whatever}" TextTrimming="WordEllipsis">
Templated version
<TextBlock Text="{Binding Comment.CommentText, FallbackValue=Comment}" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" utils:TextBlockUtils.AutoToolTip="True">
<TextBlock.ToolTip>
<ToolTip x:Name="TemplatedToolTip" Placement="Bottom">
<ToolTip.Template>
<ControlTemplate>
<Border>
<TextBlock TextWrapping="Wrap">
<Run Text="Event :" FontStyle="Italic" FontWeight="Bold"/>
<LineBreak/>
<Run Text="{Binding Whatever}"/>
<LineBreak/>
<Run Text="{Binding Comment.CommentText}"/>
</TextBlock>
</Border>
</ControlTemplate>
</ToolTip.Template>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>