Automatically Showing ToolTips on a Trimmed TextBlock (Silverlight + WPF)

January 31st, 2011 by Colin Eberhardt

Both WPF and Silverlight have a property TextTrimming=”WordEllipsis”, which trims the text that a TextBlock displays based on the available width. This blog post describes a simple method for automatically showing the full text as a tooltip whenever the text is trimmed. This is presented as an attached behaviour and works in both WPF and Silverlight

A few weeks ago I blogged about a Silverlight solution for automatically adding tooltips when a TextBlock Text is trimmed and renders an ellipsis. I found a decent looking WPF solutions on the web and linked it in my article, however, based on the comments to my previous blog post, it looks like the WPF solution didn’t work too well, failing to respect font size etc… In this blog post I have updated my solution to be cross-platform, working on WPF and Silverlight.

To briefly recap, my solution for automatically adding tooltips to trimmed text relies on the slightly odd behaviour of the TextBlock where its ActualWidth is reported as the width of the text without trimming:

In order to determine whether to show a tooltip, all you have to do is compare the ActualWidth of the TextBlock to the ActualWidth of its parent.

However, the WPF TextBlock does not have this same quirky behaviour, so a completely different approach is required. The other solutions I have seen involve using the low-level WPF drawing APIs to compute the size of the rendered text, however, there is a simpler solution to this problem …

UPDATE: I originally wrote about a method of finding the overall text width from various internal fields within the TextBlock via reflection, as shown below. However, a kind reader of my blog, Daniel Fihnn, pointed out that there is a simpler solution that does not require any reflection.

Daniel pointed out that the width of the TextBlock without the trimming can be obtained using the Measure method. This method is typically used by panels during layout, the Measure method is invoked with the size made available to the element, calling this method causes the element to update its DesiredSize property. Therefore, if you invoke Measure on a TextBlock which has trimming enabled, giving it an infinite available space, its DesiredSize property will report the width that the text would occupy without trimming.

The ComputeAutoTooltip method of the attached behaviour I described in my previous post is updated to have a completely different WPF implementation:

public class TextBlockUtils
{
  /// <summary>
  /// Gets the value of the AutoTooltipProperty dependency property
  /// </summary>
  public static bool GetAutoTooltip(DependencyObject obj)
  {
    return (bool)obj.GetValue(AutoTooltipProperty);
  }
 
  /// <summary>
  /// Sets the value of the AutoTooltipProperty dependency property
  /// </summary>
  public static void SetAutoTooltip(DependencyObject obj, bool value)
  {
    obj.SetValue(AutoTooltipProperty, value);
  }
 
  /// <summary>
  /// Identified the attached AutoTooltip property. When true, this will set the TextBlock TextTrimming
  /// property to WordEllipsis, and display a tooltip with the full text whenever the text is trimmed.
  /// </summary>
  public static readonly DependencyProperty AutoTooltipProperty = DependencyProperty.RegisterAttached("AutoTooltip",
          typeof(bool), typeof(TextBlockUtils), new PropertyMetadata(false, OnAutoTooltipPropertyChanged));
 
  private static void OnAutoTooltipPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    TextBlock textBlock = d as TextBlock;
    if (textBlock == null)
      return;
 
    if (e.NewValue.Equals(true))
    {
      textBlock.TextTrimming = TextTrimming.WordEllipsis;
      ComputeAutoTooltip(textBlock);
      textBlock.SizeChanged += TextBlock_SizeChanged;
    }
    else
    {
      textBlock.SizeChanged -= TextBlock_SizeChanged;
    }
  }
 
  private static void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
  {
    TextBlock textBlock = sender as TextBlock;
    ComputeAutoTooltip(textBlock);
  }
 
  /// <summary>
  /// Assigns the ToolTip for the given TextBlock based on whether the text is trimmed
  /// </summary>
  private static void ComputeAutoTooltip(TextBlock textBlock)
  {
#if WPF
    textBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    var width = textBlock.DesiredSize.Width;
 
    if (textBlock.ActualWidth < width)
    {
      ToolTipService.SetToolTip(textBlock, textBlock.Text);
    }
    else
    {
      ToolTipService.SetToolTip(textBlock, null);
    }
 
#else
    FrameworkElement parentElement = VisualTreeHelper.GetParent(textBlock) as FrameworkElement;
    if (parentElement != null)
    {
      if (textBlock.ActualWidth > parentElement.ActualWidth)
      {
        ToolTipService.SetToolTip(textBlock, textBlock.Text);
      }
      else
      {
        ToolTipService.SetToolTip(textBlock, null);
      }
    }
#endif 
  }
}

This code now works in both a WPF and a Silverlight context. Here’s a screenshot of it working in WPF:

You can download the full sourcecode, including the WPF and Silverlight demo: AutoTooltipTextBlock.zip

Thanks to Daniel Fihnn for a much improved WPF version of this code!

Regards, Colin E.

Tags: , , ,

12 Responses to “Automatically Showing ToolTips on a Trimmed TextBlock (Silverlight + WPF)”

  1. Sue says:

    Thanks a lot for this tip!

  2. Cele says:

    Is it possible to implement this util using a style ?

  3. Frank says:

    Hi Colin,

    Thanks for your component.
    I’m using a wpftoolkit datagrid within a grid. Initially this approach doesn’t work (when a column is resized it works well).

    If you add the following to the component all is well (just after “textBlock.SizeChanged += TextBlock_SizeChanged;”):
    textBlock.Loaded += textBlock_Loaded;

    and also a “textBlock.SizeChanged -= TextBlock_SizeChanged;” a couple lines below

    and:
    private static void textBlock_Loaded(object sender, RoutedEventArgs e)
    {
    TextBlock textBlock = sender as TextBlock;
    ComputeAutoTooltip(textBlock);
    }

    Once again thanks,

    Frank

  4. Daniel Fihnn says:

    In then WPF case, why don’t you use textBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)) and then compare textBlock.ActualWidth with textBlock.DesiredSize.Width instead of
    using reflection?

  5. [...] Automatically showing ToolTips on a Trimmed TextBlock (Silverlight + WPF) (Colin Eberhardt) [...]

  6. Tom says:

    Amazing how much useful info is hidden away in private fields eh? I’ve come across things like this on several occasions – the VS debugger makes it all too easy to find. But I dare not make use of it in a production app except for debugging. Am I being too conservative?

    • Good question, and not an easy one to answer! The TextBlock control was introduced in .NET 3.0 as part of the .NET framework. There are now versions of this control in the 3.5 and 4.0 versions of the framework. Whilst their public API is guaranteed to be the same, Microsoft could have completely changed their internals between versions.

      You are probably right to be a little cautious. ‘hacks’ like this should probably be tested across all three .NET framework versions, and guarded with suitable null reference checking.

      I will test a bit more thoroughly and report back!

  7. Automatically Showing ToolTips on a Trimmed TextBlock (Silverlight + WPF) | Colin Eberhardt’s Adventures in WPF…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

Leave a Reply