Thursday, October 14, 2010

Determining if a TextBlock has been trimmed in WPF

Many a time I want that textblocks that get trimmed to display their text as a tooltip. For that, I would make a trigger to set the Tooltip value to the Text value if the textblock has been trimmed. The problem is that there is no property that shows if this is the case.

Googling a bit, I found this article, which apparently works, but also has some problems, as reported by many commenters. Even if it would have worked perfectly, my scenario is too simple to need all that code.

The idea of the original post is simple and I like it, the problem being that convoluted method that computes the width of the text in the textblock. Why would I want to redo all the work that is being done by the framework itself? The width of a textblock with TextWrapping NoWrap should be textBlock.Measure(Size(double.MaxValue,double.MaxValue)). I am sure a more complex scenario can also be serviced by this method, if the height is taken from the TextBlock, but I don't need it.

So here is the entire class, using my measuring method:
public class TextBlockService
    static TextBlockService()
        // Register for the SizeChanged event on all TextBlocks, even if the event was handled.
        EventManager.RegisterClassHandler(typeof (TextBlock),
        new SizeChangedEventHandler(OnTextBlockSizeChanged),true);

    public static readonly DependencyPropertyKey IsTextTrimmedKey = 
            typeof (bool),
            typeof (TextBlockService),
            new PropertyMetadata(false)

    public static readonly DependencyProperty IsTextTrimmedProperty =

    [AttachedPropertyBrowsableForType(typeof (TextBlock))]
    public static Boolean GetIsTextTrimmed(TextBlock target)
        return (Boolean) target.GetValue(IsTextTrimmedProperty);

    public static void OnTextBlockSizeChanged(object sender, SizeChangedEventArgs e)
        TextBlock textBlock = sender as TextBlock;
        if (null == textBlock)
        textBlock.SetValue(IsTextTrimmedKey, calculateIsTextTrimmed(textBlock));

    private static bool calculateIsTextTrimmed(TextBlock textBlock)
        double width = textBlock.ActualWidth;
        if (textBlock.TextTrimming == TextTrimming.None)
            return false;
        if (textBlock.TextWrapping != TextWrapping.NoWrap)
            return false;
        textBlock.Measure(new Size(double.MaxValue, double.MaxValue));
        double totalWidth = textBlock.DesiredSize.Width;
        return width < totalWidth;

This would be used with a trigger, as I said at the beginning of the post:
<Style TargetType="{x:Type TextBlock}">
    <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
        <Trigger Property="Controls:TextBlockService.IsTextTrimmed" Value="True">
            <Setter Property="ToolTip" Value="{Binding Text,RelativeSource={RelativeSource Self}}"/>


Anonymous said...

Thanks! This is exactly what I was looking for. Saved me hours of searching the web for a solution.

Anonymous said...

This is great! I was looking for the same today, thanks for posting. However I had to modify a little bit because my textblock was having a Margin set. In this case I had to subtract the Margin.Left + Margin.Right from the totalWidth.