Post

How to make the ToolTip follow the mouse?

In a project of mine I needed a ToolTip. That’s easy, I know, but I want a ToolTip that follows the mouse and not the default behavior. Normally a ToolTip opens and stays at this position on the target element. After leaving the element the ToolTip closes.

So here is my solution after some searching at the www.

The simplest way

The simplest way is to use the ToolTip itself and not using a Popup (found this as answer at SO).

1
2
3
4
5
6
7
8
9
<Button MouseMove="Button_MouseMove"
        Content="Test Button 1"
        Padding="5"
        Margin="5">
    <Button.ToolTip>
        <ToolTip x:Name="tt"
                 Content="Solution for a ToolTip..." />
    </Button.ToolTip>
</Button>

Now do the moving magic at the mouse move event.

1
2
3
4
5
6
private void Button_MouseMove(object sender, MouseEventArgs e)
{
    tt.Placement = System.Windows.Controls.Primitives.PlacementMode.Relative;
    tt.HorizontalOffset = e.GetPosition((IInputElement)sender).X + 16;
    tt.VerticalOffset = e.GetPosition((IInputElement)sender).Y + 16;
}

That’s it.

button with tooltip

The ‘behavior’ way

The simple way is for a short effort ok, but doing this for any element that uses a ToolTip is not the way I want.

So I decided to create an attached property to enable/disable the movement.

1
2
3
4
5
public static readonly DependencyProperty AutoMoveProperty =
  DependencyProperty.RegisterAttached("AutoMove",
                                      typeof(bool),
                                      typeof(ToolTipHelper),
                                      new FrameworkPropertyMetadata(false, AutoMovePropertyChangedCallback));

Now you can use this property on your ToolTip in a simple way.

1
2
3
4
5
6
7
8
<Button Content="Test Button 2"
        Padding="5"
        Margin="5">
    <Button.ToolTip>
        <ToolTip local:ToolTipHelper.AutoMove="True"
                 Content="Awesome solution for a ToolTip..." />
    </Button.ToolTip>
</Button>

A complete example (incl. the ToolTipHelper class) can be found on GitHub.

Here is the code for the ToolTipHelper.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
public static class ToolTipHelper
{
    public static readonly DependencyProperty AutoMoveProperty =
      DependencyProperty.RegisterAttached("AutoMove",
                                          typeof(bool),
                                          typeof(ToolTipHelper),
                                          new FrameworkPropertyMetadata(false, AutoMovePropertyChangedCallback));

    /// <summary>
    /// Enables a ToolTip to follow the mouse cursor.
    /// When set to <c>true</c>, the tool tip follows the mouse cursor.
    /// </summary>
    [AttachedPropertyBrowsableForType(typeof(ToolTip))]
    public static bool GetAutoMove(ToolTip element)
    {
        return (bool)element.GetValue(AutoMoveProperty);
    }

    public static void SetAutoMove(ToolTip element, bool value)
    {
        element.SetValue(AutoMoveProperty, value);
    }

    public static readonly DependencyProperty AutoMoveHorizontalOffsetProperty =
      DependencyProperty.RegisterAttached("AutoMoveHorizontalOffset",
                                          typeof(double),
                                          typeof(ToolTipHelper),
                                          new FrameworkPropertyMetadata(16d));

    [AttachedPropertyBrowsableForType(typeof(ToolTip))]
    public static double GetAutoMoveHorizontalOffset(ToolTip element)
    {
        return (double)element.GetValue(AutoMoveHorizontalOffsetProperty);
    }

    public static void SetAutoMoveHorizontalOffset(ToolTip element, double value)
    {
        element.SetValue(AutoMoveHorizontalOffsetProperty, value);
    }

    public static readonly DependencyProperty AutoMoveVerticalOffsetProperty =
      DependencyProperty.RegisterAttached("AutoMoveVerticalOffset",
                                          typeof(double),
                                          typeof(ToolTipHelper),
                                          new FrameworkPropertyMetadata(16d));

    [AttachedPropertyBrowsableForType(typeof(ToolTip))]
    public static double GetAutoMoveVerticalOffset(ToolTip element)
    {
        return (double)element.GetValue(AutoMoveVerticalOffsetProperty);
    }

    public static void SetAutoMoveVerticalOffset(ToolTip element, double value)
    {
        element.SetValue(AutoMoveVerticalOffsetProperty, value);
    }

    private static void AutoMovePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
    {
        var toolTip = (ToolTip)dependencyObject;
        if (eventArgs.OldValue != eventArgs.NewValue && eventArgs.NewValue != null)
        {
            var autoMove = (bool)eventArgs.NewValue;
            if (autoMove)
            {
                toolTip.Opened += ToolTip_Opened;
                toolTip.Closed += ToolTip_Closed;
            }
            else
            {
                toolTip.Opened -= ToolTip_Opened;
                toolTip.Closed -= ToolTip_Closed;
            }
        }
    }

    private static void ToolTip_Opened(object sender, RoutedEventArgs e)
    {
        var toolTip = (ToolTip)sender;
        var target = toolTip.PlacementTarget as FrameworkElement;
        if (target != null)
        {
            // move the tooltip on openeing to the correct position
            MoveToolTip(target, toolTip);
            target.MouseMove += ToolTipTargetPreviewMouseMove;
            Debug.WriteLine(">>tool tip opened");
        }
    }

    private static void ToolTip_Closed(object sender, RoutedEventArgs e)
    {
        var toolTip = (ToolTip)sender;
        var target = toolTip.PlacementTarget as FrameworkElement;
        if (target != null)
        {
            target.MouseMove -= ToolTipTargetPreviewMouseMove;
            Debug.WriteLine(">>tool tip closed");
        }
    }

    private static void ToolTipTargetPreviewMouseMove(object sender, MouseEventArgs e)
    {
        var target = sender as FrameworkElement;
        var toolTip = (target != null ? target.ToolTip : null) as ToolTip;
        MoveToolTip(sender as IInputElement, toolTip);
    }

    private static void MoveToolTip(IInputElement target, ToolTip toolTip)
    {
        if (toolTip == null || target == null)
        {
            return;
        }
        toolTip.Placement = System.Windows.Controls.Primitives.PlacementMode.Relative;
        var hOffset = GetAutoMoveHorizontalOffset(toolTip);
        var vOffset = GetAutoMoveVerticalOffset(toolTip);
        toolTip.HorizontalOffset = Mouse.GetPosition(target).X + hOffset;
        toolTip.VerticalOffset = Mouse.GetPosition(target).Y + vOffset;
        Debug.WriteLine(">>ho {0:.2f} >> vo {1:.2f}", toolTip.HorizontalOffset, toolTip.VerticalOffset);
    }
}
This post is licensed under CC BY 4.0 by the author.