Silverlight 4 added 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.

UPDATE: I have updated this solution to support both WPF and Silverlight, see my recent blog post.

The TextTrimming feature of SL4 allows us to worry a little less about the layout of our applications, giving the option to use fixed widths when rendering text. This can be particularly useful if you have to localize your application. However, arbitrarily trimming the text without providing the user with a mechanism to see the full text is not great for the user. A simple solution is to use a tooltip to present the full text.

Detecting whether the a TextBlock's text is being trimmed is not something that is available from the TextBlock's API, there is no IsTrimmed property! A quick search of the Silverlight forums drew a dead end. I did find a decent WPF solution, however this uses various parts of the WPF API that are not present in Silverlight.

Eventually I found that there is a very simple solution ... quoting from the MSDN page on Text and Fonts:

You can detect clipped text programmatically because ActualWidth for a TextBlock always reports the expanded size of the text, even if it does not fit in the layout container. If you know where to read the Width for the layout container that is doing the clipping, you can compare these two values.

Therefore, if a TextBlock is trimming the text, the reported ActualWidth of the TextBlock will be the width that the TextBlock requires to render the text in its entirety, not the actual width of the rendered text!

In the example above, the Width of the TextBlock is set explicity, however it is more common that the width of TextBlock is determined by its parent container, a Grid for example.

To wrap this functionality up into something re-useable, I have created a AutoToolTip attached behaviour. The code for this is shown below:

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

Most of this is boiler-plate attached behaviour code. The most interesting method is ComputeAutoTooltip which determines whether a tooltip is required. This simply looks at the ActualWidth of the parent and compares it to the ActualWidth of the TextBlock.

To use this code, ensure that your TextBlock is placed within its own Grid, then set the attached property to true. You can see this in action below:

<Grid util:GridUtils.ColumnDefinitions="2*,"
      util:GridUtils.RowDefinitions=",">

  <sdk:GridSplitter Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
                    ShowsPreview="True"
                    Background="LightGray"
                    Width="10"
                    HorizontalAlignment="Right"/>

  <Grid Grid.Column="0"
        Margin="0,0,10,0">
    <TextBlock Text="The rain in Spain stays mainly in the plain"
                util:TextBlockUtils.AutoTooltip="True"/>
  </Grid>

  <Grid Grid.Column="2">
    <TextBlock Text="In Hertford, Hereford, and Hampshire, hurricanes hardly ever happen"
                util:TextBlockUtils.AutoTooltip="True"/>
  </Grid>

  <Grid Grid.Column="0" Grid.Row="1"
        Margin="0,0,10,0">
    <TextBlock Text="Peter Piper picked a peck of pickled peppers"
                util:TextBlockUtils.AutoTooltip="True"/>
  </Grid>

  <Grid Grid.Column="2" Grid.Row="1">
    <TextBlock Text="She sells sea-shells on the sea-shore"
                util:TextBlockUtils.AutoTooltip="True"/>
  </Grid>
</Grid>

Note the use of the simplified grid syntax which I blogged about earlier. You can see this code in action below. Move the grid splitter to see the tooltips automatically enabled when an ellipsis is displayed:

You can download the full sourcecode here: AutoTooltipTextBlock.zip

Regards, Colin E.