How can I automatically show a tooltip if the text is too long?
Asked Answered
M

3

4

In a Windows Store Application I have the following TextBlock:

        <TextBlock Text="Seriously long text for the purpose of showing tooltip"
                   TextTrimming="CharacterEllipsis" />

How do I automatically show a tooltip when the text is too long to display without an ellipsis?

Moderator answered 6/2, 2014 at 22:37 Comment(0)
S
6

Here's my solution, based on this and this.

First, create an attached property to enable auto tooltip:

public static class TextBlockUtils {
    public static readonly DependencyProperty AutoTooltipProperty =
        DependencyProperty.RegisterAttached ("AutoTooltip", typeof (bool), typeof (TextBlockUtils),
                                             new PropertyMetadata (false, OnAutoTooltipPropertyChanged));

    public static void SetAutoTooltip (DependencyObject d, bool value) {
        d.SetValue (AutoTooltipProperty, value);
    }

    public static bool GetAutoTooltip (DependencyObject d) {
        return (bool) d.GetValue (AutoTooltipProperty);
    }

    private static void OnAutoTooltipPropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var tb = d as TextBlock;
        if (tb != null) {
            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;
        if (tb != null) {
            SetTooltipBasedOnTrimmingState (tb);
        }
    }

    private static void SetTooltipBasedOnTrimmingState (TextBlock tb) {
        bool isTextTrimmed = tb.ActualWidth < tb.DesiredSize.Width;
        ToolTipService.SetToolTip (tb, isTextTrimmed ? tb.Text : null);
    }
}

Then use it in XAML like so:

<TextBlock Content="long text"
           TextTrimming="CharacterEllipsis"
           TextBlockUtils.AutoTooltip="True" />

The tooltip will only be shown when the textblock is trimmed.

Surcharge answered 18/9, 2015 at 15:1 Comment(0)
F
0

Usually you tap it and open a view where it shows up in full either because it has more space/uses smaller font or where the text wraps/scrolls.

Fagin answered 6/2, 2014 at 23:44 Comment(1)
It's a good solution, but not in my case. I'd omitted code for brevity, but my textblock is the content of a button, and tapping the button is already bound to a navigation command. I really need a tooltip on hover, but only if the text is too long to display without an ellipsisModerator
C
0

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>
Colettecoleus answered 17/2, 2023 at 11:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.