This control extends the standard ToolTip with several possibilities like retrieving a ToolTip asynchronously from an external (e.g., Internet) source. It is easy to implement and to extend.
For years I’ve been struggling with the idea of using the
ToolTip control that is offered by the .NET Framework in Windows Forms applications. On one hand it is a nice control that offers the possibility of easily integrating visible tooltips. On the other hand, it is not possible to extend it in any way. Since I am currently working on a major update of one of my applications, I wanted to include some fancy tooltips that can come from an arbitrary data source.
ToolTip control does not include two major customization features: drawing it at will and using the data at will.
InternetToolTip creates the basis for writing our own data providers (what to do with the tooltip string) as well as our own view providers. The ones that I’ve written give
InternetToolTip the same abilities that the standard control has – and more.
This code requires C# (Version 2.0 and higher) together with Microsoft’s .NET 2.0 or higher. The technology used is Windows Forms. However, Windows Forms is just required for the test application / in combination with the sample controls and sample view. Basically it should be possible to port this to other presentation technologies or even ASP.NET. However, I suppose some changes in the inheritance of the main class,
InternetToolTip, would be required as well. So the project mainly targets Windows Forms.
InternetToolTip control should offer the possibility to be fully customized, i.e., displaying data in a way the programmer wants to have it. The data source should not depend on the control itself but could be provided by the programmer as well.
The following goals should be achieved:
- The data view and the data source can be fully customized or rewritten without rewriting the control itself.
- The control should be similar to the existing tooltip control, i.e., it should be possible to not do any rewriting and have about the same functionality.
- The control should support asynchronous requests.
- Due to similar syntax, the efforts of replacing the old tooltip control with the new one should be minimal.
All those goals are nice but a little bit too general. What are my personal goals for this control?
- I want to write a (more sophisticated than the provided) webservice data provider that fetches the data from a specific webservice.
- The tooltips provided in the form are used as data request strings, i.e., they will be one of the arguments of the webservice call.
- The return value is used in order to draw some information and can be used in order to open a browser with an even more detailed help entry.
The sample application
The provided code builds the basis for the tooltip control and derived controls. I’ve included two basic controls:
- A really simple light bulb control. Its usage is really simple – it’s just a custom image (default is the light bulb displayed).
- A more complex tooltip textbox control. It extends the standard textbox with a cue and displays a tiny light bulb behind the textbox. This control actually has an
InternetToolTip control included – here you’d only need to provide a custom data provider and data view if necessary.
I’ve also included a basic data provider as well as a basic data view. Both aim to give
InternetToolTip the same basic functionality as the standard tooltip control. The view control goes a little bit further by containing some animation functions like:
Appear (the tooltip will just appear),
Fade (the tooltip will fade in),
Slide (the tooltip will slide down from the top of the screen) and
SlideFade (the tooltip will fade in and slide down from the top of the screen).
Those animations can run for any amount of time (specified in ms) that is set (e.g., 500ms, 2000ms, ...).
The provided solution contains a sample implementation of a custom data provider. Here I simulate the work of fetching some data from an arbitrary data source (internet website / service, database …) with an instance of a
Timer class. The
Tick event is used for simulating an asynchronous request while the
Thread.Sleep() method is used to simulate a synchronous request. This should display the power of asynchronous requests.
In order to stay flexible, I used interfaces. Interfaces give us several advantages over using (abstract) classes. First of all, it does not restrict you from inheriting from an existing class, which means you do not have to inherit from my interface directly. Just use the construction plan where you need it. Secondly, I did not have to repeat the
abstract keyword everywhere, since interfaces can only contain abstract methods and properties. I also did not have to specify that those methods are
virtual since every method in an interface is virtual. So I got a cleaner code (less keywords required), which provides more flexibility.
InternetToolTip control relies on two interfaces:
IToolTipView – this is responsible for showing the display on screen. I’ve provided a sample one which is similar to the view we gets with the standard
ToolTip control. This view has some advantages. I will go into the details later on.
IToolTipDataProvider – here we fetch the data. To not make things complicated, I just used one data string. This one data string can be represented as a tooltip using the sample implementation I’ve provided. However, the data string is just responsible for differentiating different tooltips – not the tooltips themselves. So in your (free) implementation of a data provider, you might use the data string for fetching the real tooltip from some database or some other resource. The sample control just returns the data string and thus simulates the behavior of the real tooltip control.
A data view has to contain the following methods:
void ShowToolTip(Point pt, Size sz);
void DrawLoading(string text);
void DrawToolTip(object tooltip);
void DrawException(Exception exception);
This ensures that a tooltip can be shown at some coordinate, where the upper left coordinates and the size of the control (which requests this tooltip) are passed as arguments. The coordinates are always passed as screen coordinates. Also, the
InternetToolTip control can tell the view provider to stop displaying its information. This is the case when the mouse is leaving the tooltip area. The draw methods inform the view about a change in the state. If an asynchronous request is started, the loading screen has to be rendered. If the request was returned successfully, the tooltip has to be drawn. In case of an exception, a special routine is required.
A data provider has to contain the following methods:
object RequestData(string request);
void RequestDataAsync(string request, Control userState);
void CancelDataAsync(Control userState);
event ToolTipDataEventHandler RequestDataHandled;
This ensures that data can be requested synchronously and asynchronously. The latter one has a callback which is implemented as an event. The event is integrated with a new delegate containing a more detailed
EventArgs implementation. The given more specific
Because several (asynchronously requested) requests could take place, we need a state to determine which tooltip has been requested. This is done over the control that contains the tooltip. This could be used for caching tooltips as well!
The result is saved as an object since I have all kinds of objects in my mind. One idea I had was to fetch more data and return it as a preview string with a longer one and an internet URL for the most detailed version. So I would need three information pieces (“Preview”, “View”, “URL”), which would require a special dataview. In order to be flexible, I decided to pick
object and use the
ToString() method in my general implementation of a data view. So if you want to transport custom objects, which should be painted in a custom way, you have to rewrite my basic view provider or implement your own one. In any case, you do not have to touch
InternetToolTip or the data provider interface.
Success determines if the request was successfully returned, i.e., if there was an exception. The exception is saved as well and could (should) be displayed. The basic view I’ve provided displays the exception.
Here are the properties of the
Those are the variables that can be changed with the corresponding properties. The first two are just placeholders for specific implementations of those interfaces. With help of the
Async property, you can decide if you want to use the asynchronous or the synchronous requests. The
LoadText property sets a string that is shown while loading a tooltip.
Implementation was (as usual) not always straightforward. I will go into the details of the most interesting parts.
The control itself is a
Component. This means we are at some basic level with not much overhead. Since we do not want the control to be displayed (directly), there is no need to inherit from
Control or other more sophisticated classes.
public class InternetToolTip : Component, IExtenderProvider
I did also use the interface
IExtenderProvider to tell the designer about the possibility of extending controls that offer the following abilities:
public bool CanExtend(object extendee)
return (extendee is Control && !(extendee is Form) &&
!(extendee is InternetToolTip));
This means only controls and not forms or the
InternetToolTip control itself can be extended. Since I set that the property
ToolTip should be provided with the control, I had to create two methods:
public void SetToolTip(Control control, string caption)
control.MouseEnter -= new EventHandler(control_MouseEnter);
control.MouseLeave -= new EventHandler(control_MouseLeave);
collection[control] = caption;
control.MouseEnter += new EventHandler(control_MouseEnter);
control.MouseLeave += new EventHandler(control_MouseLeave);
public string GetToolTip(Control control)
If the designer or somebody wants to set a tooltip with an empty string, it will either remove the control from the collection of tooltip-controls (including all events) or do nothing. Else it will either change the tooltip that has been set for this control or add the control with the provided tooltip to the collection. In this case, we have to set the events as well.
The get method just looks for the specified control in the collection. If the collection does not contain the control, then an empty string is returned (this means there is no tooltip set for this control).
The real interaction comes with the control events (in this case: mouse-enter and mouse-leave). The code reads:
void control_MouseLeave(object sender, EventArgs e)
dataProvider.CancelDataAsync(sender as Control);
if ((sender as Control).Equals(active))
loading = false;
void control_MouseEnter(object sender, EventArgs e)
loading = async;
Control c = sender as Control;
active = c;
Point p = c.Parent.PointToScreen(c.Location);
catch (Exception ex)
void OnRequest(object sender, ToolTipDataEventArgs e)
loading = false;
So what is happening here? First of all, if the mouse leaves the control, we want to hide the tooltip. This seems straightforward but we have also to think about the asynchronous case as well. Maybe the data provider has implemented a cancel method. So, if we are still loading (only possible if we have set
true), then we should call the cancel method of the asynchronous data request. Even more, if the control is the active control (the one we are looking at right now), then we can set
false since we just left the loading mode.
The next thing is corresponding to the event of the mouse pointer entering a control. The status of
loading is determined by the general status of
async. In non-asynchronous mode, we will call the method directly in order to get the tooltip string – therefore there won’t be any loading. Next, the active control is set by the one that was just being entered. This is determined by the sender. We can do this cast – since the
CanExtend() method made sure that just controls which have inherited from
Control can be extended and thus enter our collection / our event system.
After reading out the current screen location, we call the show method of our view and request our tooltip string. The
async mode is quite straightforward (we set loading and then place the asynchronous request which will fire the event in case of success or failure) while the other one is wrapped with a
try-catch-block. This is the only way of getting an exception as a feedback from this non-asynchronous request, since the result will always be of type
object. We do not know if an object of type
Exception means that an exception occurred. So this should provide some flexibility.
The last part of the code represents the method that is triggered once the request has been finished. Here we just take a look at the state – if the control is not the current one, then the call is obsolete. Otherwise we distinguish between a successful call and an exception.
Let's have a look at the provided implementation of a data provider. I've included the
StringDataProvider in order to give my control the same functionality as the ordinary
ToolTip control right away:
public class StringDataProvider : IToolTipDataProvider
public object RequestData(string request)
public void RequestDataAsync(string request, Control userState)
if (RequestDataHandled != null)
RequestDataHandled(this, new ToolTipDataEventArgs(request, userState));
public void CancelDataAsync(Control userState)
public event ToolTipDataEventHandler RequestDataHandled;
This is a really simple implementation, which just gives back the passed argument. Therefore we can omit the
CancelDataAsync. However, due to restrictions using the interface we have to implement the method. So we just set a blank method body.
A little bit more interesting is the provided data view implementation. I’ve called this one
BasicToolTipView. After some research, I figured out that using a top-most form will be the solution for having an organized way of drawing the tooltip.
public partial class BasicToolTipView : Form, IToolTipView
const int SW_SHOWNOACTIVATE = 4;
const int HWND_TOPMOST = -1;
const uint SWP_NOACTIVATE = 0x0010;
const int MS_PER_FRAME = 13;
public event DrawToolTipEventHandler DrawToolTipView;
public event MeasureToolTipEventHandler MeasureToolTipView;
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(...);
static extern bool ShowWindow(...);
public void ShowToolTip(Point p, Size sz)
Location = new Point(p.X + sz.Width / 2 - Width / 2, p.Y - Height);
SetWindowPos(Handle.ToInt32(), HWND_TOPMOST, Left,
Top, Width, Height, SWP_NOACTIVATE);
if (ShowEffect == Effect.Appear)
public void HideToolTip()
if (frames > 0)
public enum Effect
Appear, Fade, Slide, SlideFade
First of all, this form not only inherits from
Form but also from
IToolTipView. This is why I picked interfaces – being totally flexible. What was difficult about this implementation and why is it a good implementation? Creating a top-most form is not really a hard task in .NET. Preventing the form from stealing focus is difficult. However, some smart guys have already made good research and found the right API calls to give our form this (missing) ability. I’ve provided the links that helped me on this topic in the References list. The trick is to use the right DLL calls with the right methods and the right constants. All in all, it’s all about the right call! This is why the methods
HideToolTip() will usually excel standard
Hide() methods that are implemented in Windows Forms. My show method does the following things:
- It sets the location to the horizontal center and the vertical top of the control it is used on.
- It uses the Win-API call to show itself – the difference from the usual call is the constant that tells the Operating System not to give focus to the shown form.
- It uses the Win-API call to place itself top-most.
- It does some things depending on the animation that is set. If the effect is in
Appear mode, it just appears. In fade, it will calculate how much opacity is to be gained per
Tick event and set the starting opacity to 0. Slide will have a similar calculation.
- If an animation (effect different than
Appear) has been set, the timer is started.
My hide method checks if an animation is running and cancels the animation. Then of course the
Hide() method is used to hide the form.
Close() is not possible here, since we do not want to recreate the form for every tooltip – just show at a different location with a different content.
Effect enumeration is nested in the class to show the relation. It provides the possible animation effects. Right now there is basically "no animation", "slide from top", "fade in", and a combination of those two effects. Additionally, the duration of the animation can be set. All this (effect and duration) can be done by properties or the following method:
public BasicToolTipView Animation(Effect effect, int duration)
ShowEffectTime = duration;
ShowEffect = effect;
This method sets both properties in one call and returns the current instance. This is called chaining. Such a concept allows the user to make use of else non-returning methods.
I included a possibility to use my premade control (so my "sample" tooltip view provider) with custom methods for drawing. As with the
ListBox and other Windows Forms controls, this is done by setting a property (called
OwnerDrawVariable and handling events triggered by my view provider.
The standard drawing will create such a bubble shape:
The height is usually 32 pixels. This is only important for loading messages. Real tooltips will not be restricted to a content height of 18 pixels. So those 18 pixels can be seen as a kind of minimal height for the content. To display real tooltips, a maximum width has to be specified. The height then grows as the maximum width does not provide enough space to display the tooltip. Measuring the text size is done with the static
TextRenderer class that provides string drawing and measuring capabilities, which are really useful in combination with proposed heights and widths. Those methods also work nicely together with the
Using the code
You can simply use the control with the designer. Since I've built the control to mimic the original
ToolTip control, the way you would use my control is in principle the same. However, if you want to go beyond standard abilities, you have to use the code-behind. The following code creates a new instance of my tooltip control (can be done over the designer, too) and customizes the providers:
InternetToolTip toolTips = new InternetToolTip();
toolTips.LoadText = "Please wait!";
toolTips.DataProvider = new SampleDataProvider();
BasicToolTipView toolView = toolTips.DataView as BasicToolTipView;
toolView.GradientColorOne = Color.LightBlue;
toolView.GradientColorTwo = Color.LightGray;
BasicToolTipView can be drawn by using the following properties and events:
toolView.DrawMode = DrawMode.OwnerDrawVariable;
toolView.MeasureToolTipView += new MeasureToolTipEventHandler(measureToolTipView);
Here we have assigned the two important events to callback methods in our form / control. Those functions could be implemented like this:
void measureToolTipView(object sender, MeasureToolTipEventArgs e)
e.Height = TextRenderer.MeasureText(e.ToolTip.ToString(),
e.Font, new Size(e.Width, e.Height)).Height;
void drawToolTipView(object sender, rsi.Controls.iToolTip.DrawToolTipEventArgs e)
TextRenderer.DrawText(e.Graphics, e.ToolTip.ToString(), e.Font, e.Bounds, e.ForeColor,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
Points of interest
Even though it was not the focus of this project, I did also build a very small control (for the sample application – but it could be useful in other applications as well) that is nothing more than an (selectable) image. However, the size of the control is always exactly the size of the image. The default image is a light bulb (thus
LightBulb control). Additionally, I built a larger control which is a (cue) textbox with an integrated
InternetToolTip. I built this control since it shows how the control could be used in any custom control. Another reason is to show in which direction I want to go with
InternetToolTip: having specialized controls that provide their own tooltips in a custom design.
For me integrating the animation was quite spectacular. It was something I wanted to implement in a Windows Forms application for a long time. I was curious about the performance and I did like the result. I suppose that writing an animation framework for a Windows Forms application would be quite handy and useful. One really good approach is already published here on CodeProject.
I found the following links useful:
- The problem of showing a form without stealing focus was solved on StackOverflow (here).
- An introductory discussion was necessary in order to determine an efficient way to do the screen drawing (here).
- A short article about handling exceptions (here).
- v1.0.0 | Initial release | 01.12.2011.