ToolTip Voodoo
Introduction
ToolTips can be used in a variety of ways to provide additional information to the user. Now, understand, sometimes the usage of these can be abused and misused. However, used correctly, ToolTips can improve the UI of your application tremendously.
I’m going to state up front that this article is not about how to correctly use tooltips; rather, it’ll arm you with the necessary basic information to create custom tooltips that can present pretty much any information you desire; well beyond a simple line of basic text in a yellow box.
Yes, this means that I’ll be arming you with the information to potentially create one of the most gawd-awful UI’s you’ve ever seen; however, use appropriately you can create extremely compelling tooltips that can significantly enhance your application.
Initial Setup of our Project Environment
- Create a new Windows Form project (
CustomToolTips
). - Add a ToolTip control to
Form1
. - Add a Label control to
Form1
(Name
= “Label1”,Text
= “Default ToolTip”,ToolTip
onToolTip1
= “This is the ToolTip for this Label.”). - Add a Button control to
Form1
(Name
= “Button1”,Text
= “Custom ToolTip”).
First Things First
At this point, you can execute the application and you will find that moving your mouse over the label that a ToolTip will become visible. At this point, it’s the regular tooltip you’ve come to love (and hate).
Level Up…
The first step to controlling how ToolTips are drawn is to set the OwnerDraw
boolean property to True
. Now the Draw
event will be activated and you can now control how the ToolTip is drawn.
The first thing we will do is reproduce the drawing behavior of the default ToolTip so that we can modify it from there. To do this, you should have a Draw
event that looks as follows:
1
2
3
4
5
6
7
8
Private Sub ToolTip1_Draw(sender As Object, e As DrawToolTipEventArgs) _
Handles ToolTip1.Draw
e.DrawBackground()
e.DrawBorder()
e.DrawText()
End Sub
If you run the application again, you’ll find that the ToolTip looks like the first time you ran the application. Not to exciting, but as you can see that the built in functionality of the ToolTip control has already broken down portions of the drawing into individual segments. Specifically, the DrawBackground
(draws the yellow background), DrawBorder
(draws the black single line box that borders the yellow area) and DrawText
(that draws the text using the default formatting used by the ToolTip control) methods are available to you. So, let’s level it it up further and replace the dull single color yellow background with a gradient of some sort. In this example, we will just start with white to a yellow gradient.
Add the following import to the top of the code file so we can save ourselves a bit of typing:
1
Imports System.Drawing.Drawing2D
Please note that the code in this article assumes that
Option Infer
is enabled (either at the project or file level).
Modify the Draw
event as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Private Sub ToolTip1_Draw(sender As Object, e As DrawToolTipEventArgs) _
Handles ToolTip1.Draw
' Draw the custom background (replacing e.DrawBackground()).
Using brush As New LinearGradientBrush(e.Bounds,
Color.AntiqueWhite,
Color.Yellow,
LinearGradientMode.Vertical)
e.Graphics.FillRectangle(brush, e.Bounds)
End Using
e.DrawBorder()
e.DrawText()
End Sub
As you can see, pretty simple to change out the default drawing of the background with your own with just a couple of lines of code. So now your ToolTip, although not really too much different than the default, is spiced up a bit; of course, you could use any colors you wish based on the overall look and feel of your application.
One of the key factors with this particular example is the usage of the Bounds
property. This property is similar to a Control’s ClientRectangle
property and defines the boundary of the ToolTip’s drawing area. Utilizing this, we can draw the background accordingly so that it’s the correct size.
All my ToolTips are now “messed up”!!!…
At this point, every control that is utilizing ToolTip1
will be drawn in this manner. However, if you’d like to control this behavior and have some controls drawn one way and other drawn another, you can approach this two ways.
The simplest method is just to utilize more than one ToolTip control. By setting the ToolTip text for the control and ToolTip in question, the tooltip will be handled accordingly. However, if you’d like to control it yourself, you could use a single ToolTip control and handle the drawing based on the control that raised the Draw
event.
Here’s an example will only draw the custom version if the control is Button1
and any other controls will be drawn using the default behavior.
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
Private Sub ToolTip1_Draw(sender As Object, e As DrawToolTipEventArgs) _
Handles ToolTip1.Draw
If e.AssociatedControl Is Button1 Then
' Draw the custom background (replacing e.DrawBackground()).
Using brush As New LinearGradientBrush(e.Bounds,
Color.AntiqueWhite,
Color.Yellow,
LinearGradientMode.Vertical)
e.Graphics.FillRectangle(brush, e.Bounds)
End Using
e.DrawBorder()
e.DrawText()
Else
' Any other control utilizing ToolTip1.
e.DrawBackground()
e.DrawBorder()
e.DrawText()
End If
End Sub
Which approach is better? I really don’t think one approach is superior to the other and suspect it’s simply a matter of personal preference (or possibly specific scenario(s)). I chose to use a single ToolTip; but you could approach this in either manner you desire.
Now let’s really level things up…
Let’s control everything; let’s draw a custom background, handle the way text is drawn, add a divider and show an image. Let’s jump into the code:
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
Private Sub ToolTip1_Draw(sender As Object, e As DrawToolTipEventArgs) _
Handles ToolTip1.Draw
If e.AssociatedControl Is Button1 Then
' Draw the custom background.
Using brush As New LinearGradientBrush(e.Bounds,
Color.AntiqueWhite,
Color.CornflowerBlue,
LinearGradientMode.ForwardDiagonal)
e.Graphics.FillRectangle(brush, e.Bounds)
End Using
' Draw the border (default).
e.DrawBorder()
' Draw the "title" text.
Dim x = 0
Dim y = 0
Dim titleHeight = 40
e.Graphics.DrawLine(Pens.Black, 0, titleHeight, e.Bounds.Width, titleHeight)
Dim text As String = "Now this is a TOOLTIP!"
Using font As New Font(e.Font, FontStyle.Bold)
x = (e.Bounds.Width - e.Graphics.MeasureString(text, font).Width) \ 2
y = (titleHeight - e.Graphics.MeasureString(text, font).Height) \ 2
e.Graphics.DrawString(text, font, Brushes.Black, x, y)
End Using
' Draw an image.
Dim image = New Bitmap(My.Resources.CorySmith)
Dim w = image.Width \ 4
Dim h = image.Height \ 4
x = (e.Bounds.Width - w) \ 2
y = titleHeight + (((e.Bounds.Height - titleHeight) - h) \ 2)
e.Graphics.DrawImage(image, x, y, w, h)
Else
' Any other control utilizing ToolTip1.
e.DrawBackground()
e.DrawBorder()
e.DrawText()
End If
End Sub
The above code assumes that you’ve added a new resource item (a valid image file) to the project called CorySmith
. (The attached project has this done already.) When you run this version, you’ll see that whenever you move your mouse over the Label, it shows the default ToolTip version. Moving over the Button control displays some pinkish colored image, not what we were wanting. The issue is that when the ToolTip is drawn, it’s initial size is calculated based on the current ToolTip text value. So what we need to do is intercept the initial drawing and set the size that we need. One way (and it’s the wrong way for this example) would be to set the text value (with possible padding) so that the control knows how to draw. In this example, this would not work since we are drawing text and an image and completely formatting all of the elements with custom spacing between the elements.
How to set the ToolTip size…
It would seem that we could just set the Bounds
property in the EventArgs
, but this value is read-only. However, the ToolTip control does allow you to intercept the Draw
event before it occurs and control the size of the bounding rectangle. This event is called Popup
. In this event, you can specify the ToolTipSize
that will be utilized during the Draw
event.
1
2
3
4
5
6
7
8
9
10
Private Sub ToolTip1_Popup(sender As Object, e As PopupEventArgs) _
Handles ToolTip1.Popup
If e.AssociatedControl Is Button1 Then
e.ToolTipSize = New Size(400, 300)
Else
' Do nothing.
End If
End Sub
So if the control is Button1
, then we’ll set the size (fixed in this example) to 400x300 pixels. For all other controls, we aren’t modifying the default behavior.
Now when we run our example, the Button1
ToolTip is drawn in the manner we were expecting.
Conclusion
As you can see, it’s not really all that difficult to create feature a rich owner drawn ToolTip. Now I’m only demonstrating showing some simple text and an image on a gradient background, but you could show almost anything. The only limitation is your imagination.
Download
Article updated November 28th, 2021