Everyone knows the ToolTip control (the programmer ones!!) which are included in the Common Controls version 4.0 since Win95 (that’s what saw first, I haven't ever caught the Win3.11). Since then the control itself have been modified and enhanced in many ways, and after a while, WinXP came to life and the Common Controls version 6.0 saw light with the Balloon ToolTip control included, which is the subject of this article.
Neither creating a Balloon ToolTip nor implementing an
IExtender is a new subject, not even a hard one, but getting the functionality of both techniques could have some attention, and that's what this article discusses: how to combine the good of both, in a way that's as simple as using the native .NET
While searching around, I found a great article about the Balloon ToolTip control (actually about the
ToolTip control, in all its shapes and uses). This article (which could be found at CodeProject too) was a great reference to me and a good, live example of using the Balloon ToolTip control, but the control described suffered from its complexity (not a real complexity, but it had to be operated programmatically), something that pushed me to investigate the
IExtender and to accomplish this work.
Reading this article would give you a general understanding of the basic ideas discussed here, but to have a complete understanding of every concept, you must have some knowledge of the Win32 API calls and their uses. You don't have to be a professional to get it right and shouldn't be a fresh guy either (you shouldn't have to jump to the nearest programming book you have to figure out what is the Win32
LPTSTR or how to allocate some bytes in the unmanaged heap). I know that was the first .NET promise - the ability to avoid using the Win32 APIs, but if you didn't knew, the APIs are not dead yet, and will not die soon, so you must get yourself comfortable using them in your own code.
Using the code
After this heavy theoretical talk, we can start getting our hands dirty.
The IExtenderProvider Interface
IExtender interface's idea is to provide a service to another control, to give the functionality you wrote not to your class but to other classes. A problem you solve in your code is presented to other classes to be used and not used directly by your own class as all ordinary classes do. In other words, you solve other components' problems in their own territory.
This interface provides a single method
CanExtend, which expects you to have an object passed to it, and it returns a boolean as an answer. The object passed to this method represents the selected control at design time, and the
bool result specifies if the class that implements this method should provide its services to this object. That's the whole story simplified.
For almost any control that implements the
IExtender interface, the
CanExtend method should return
true for each control it should extend, except itself, and any other control that may be illogical to support, and in our case, every control is more than welcome to get served except for this control and the
Form control too.
public bool CanExtend(object extendee)
if(extendee is control && !(extendee is BalloonToolTip)
&& !(extendee is From))
Based on the result of this call, the VS designer, or any other third-party designer used, decides whether to provide the specified service to the control or not, so when you select a control, the designer calls this method at design time, passing the selected control to it as a parameter, and if its get
true, the provided functionality would appear in the selected control property page (and in the code too) as a new property, exactly as if it was implemented in the original control code.
The ToolTip Control
The ToolTip control is somehow confusing while reading about it in the MSDN, and to get it right, you should distinguish between two concepts, the
ToolTip control itself and the tool it supports. The first one, the
ToolTip control, is the parent window which draws the text and behaves as it is ordered to, and the tool is the logical structure that contains the text to be drawn and controls how to display the tip. For this reason, a single
ToolTip control can have as many tools as you want, one for each control you choose.
That's it. You create the
ToolTip control which is independent from its associated tools, and has its own general properties (its width, color and period of appearance). Then you create as many tools as you need, and you add these tools to the
As with any other control, by supplying the
CreateWindowEX API function with the correct parameters, a
ToolTip control could be created, and a handle to it would be returned. For information regarding parameters usage and their meaning, you may refer to MSDN and have a complete description of each of them, but for this article's sake, it's as follows:
Toolwindow = CreateWindowEx(0, "tooltips_class32", string.Empty,
WS_POPUP | TTS_BALLOON | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, IntPtr.Zero, 0, 0, 0);
The most important of these constants is the
TTS_BALLOON which enforces the function to create a
ToolTip control that has a cartoon-style balloon. The second parameter specifies the class from which to create the window (in this case, the
ToolTip class), and for the parent parameter, we pass a zero pointer to indicate that this window has no parent (it's a popup window). And finally, the return value is a handle that represents our
ToolTip control for the lifetime of the class.
For now, we have a
ToolTip control ready to be used, but how do we communicate with it? From the .NET perspective, this
ToolTip control is the core of our component, but from the Win32 perspective, it's just a window, and to manipulate it, we have to follow the Win32 message based communication rules through the
SendMessage API function.
ToolTip control defines a number of messages to be sent to it as commands that specify its appearance and its behavior. These messages are listed in MSDN, and our control doesn't use all of them, just the ones that are useful for our implementation.
TTM_ACTIVATE: Enable and disable the
ToolTip control itself.
TTM_ADDTOOL: Add a tool to the
TTM_DELTOOL: Delete a tool from the
TTM_SETTITLE: Add a title to the
ToolTip balloon window.
TTM_SETTIPBKCOLOR: Set a custom background color to the
ToolTip balloon window.
TTM_SETTIPTEXTCOLOR: Set a custom foreground color to the
ToolTip balloon window text.
TTM_SETDELAYTIME: Set the time for the
TTM_UPDATETIPTEXT: Update the
ToolTip balloon window text.
TTM_SETTOOLINFO: Associate the specified tool with the specified
TTM_GETTOOLINFO: Gets the
TOOLINFO structure associated with the specified tool.
This summarized list is by no means a reference to the messages that the
ToolTip control uses. For a detailed description of each of these listed and any not listed messages, refer to the MSDN documentation. Most of the messages have meaningful names and don't need any further explanation.
TTM_ADDTOOL message is our key to add a tool to the
ToolTip control, and as I said before, the
ToolTip control is separated from its associated tools. The
ToolTip control does the housekeeping work and displays the balloon window with the specified setting, and each tool of its associated tools is a
TOOLINFO structure that holds the information needed to accomplish displaying a balloon window.
Take a look at the
TOOLINFO structure here:
typedef struct tagTOOLINFO
#if(_WIN32_IE >= 0x0300)
This structure represents a tool contained in a
ToolTip control. The
cbSize member must specify the size of this structure in bytes, the
uFlag member controls the display of the
hwnd is the handle to the control (the control in your form design) that contains this tool,
rect is the member which holds the
ClientRectangle of the control that contains this tool, and
lpszText is the buffer that contains the string to be displayed in the ToolTip balloon window.
Leaving The Managed World
If you have to deal with unmanaged code like these Win32 API calls, let me introduce you to a good friend, the
System.Runtime.InteropServices.Marshal class. For any travel outside the managed world, this tough guy is your best friend. There are handy useful static functions in the
Marshal class that would solve almost all your problems with unmanaged code, and one of them shows how to convert a managed version of the
TOOLINFO structure into a memory pointer to be passed into the
SendMessage API function. This function talks the unmanaged language and cannot accept your managed
TOOLINFO object, so you have to go a little bit low-level to workaround this situation, and here is how:
toolinfo tf = new toolinfo;
tf.size = Marshal.SizeOf(typeof(toolinfo));
tf.flag = TTF_SUBCLASS | TTF_TRANSPARENT;
tf.parent = targetcontrol.Handle;
tf.text = "Tooltip text to be displayed";
tempptr = Marshal.AllocHGlobal(tf.size);
Marshal.StructureToPtr(tf, tempptr, false);
SendMessage(tooltipptr, TTM_ADDTOOL, 0, tempptr);
For those who started scratching their heads (asking what the hell was that?) if any, I'll just say that explaining about how to work with unmanaged code is beyond the scope of this article. Anything that seems to you to appear either in the
Marshal class or in the
ToolTip constants and messages, is explained shortly in the comment, and if you don't get it yet, you have to refer to the MSDN (if you still cannot get it, then either I'm a bad writer or you are a bad reader).
Are We Done With The IExtender?
As an answer, not yet. There is a little detail that's not been discussed yet. The
IExtender interface provides an extended property to other classes, but our class that implements the
IExtender must be marked with the
ProviderProperty attribute. This attribute along with the
IExtender accomplishes this task. This attribute constructor accept two parameters, a string specifying the name of the property your class will provide to other components, and the type of the receiver of this extended property.
public class BalloonToolTip : System.ComponentModel.Component,
After adding this attribute, and when we add our control to the designer, every supported control on the form will have a new property added to its properties, named as the text specified in the
ProvideProperty attribute (
Now we have done marking our class with its provided property name and receiver type, but this is still not enough from a code perspective. In our code, there must be a Get/Set pair of functions with exactly the same name as our extended property. To put it another way: we specified “
BalloonText” as the name of our new property and we have to supply the “
GetBalloonText” and the “
SetBalloonText” functions as well.
The Get function is a function that returns a string (surprised? !!) which is the string associated with the control passed to it as a parameter. This parameter must be the same type as the receiver type in the
public string GetBalloonText(Control parent)
And the Set function is a
void function (surprised too? !!) and expects to have two parameters passed to it, the control that you want to add the property value to it, and the string value to be added.
public void SetBalloonText(Control parent, string value)
These functions do not appear as ordinary functions in the code, but as properties in the control they have extended, so don't get confused (after all, that's what the
IExtender should do).
The last thing to say is that it's your choice how to implement these functions. You may have a
Hashtable to hold the values, getting it right from the tool the control contains, having an array with somehow the correct index to store these values, or you might even write a whole new class to do this job, but it's your choice, and I chose the
The hash table is a place to store a collection of key/value pairs, and in our case, we already have these key/value pairs. It's our control/property pair. For each control, we have a unique string as its
BalloonText value, so I used a hash table to store these facts. The control is the key and its associated “
BallonText” is the value.
For the Get function, there is nothing more to say, but for the Set function there is:
public void SetBalloonText(Control parent, string value)
if(value == null)
value = string.Empty;
if(value == string.Empty)
hashtable[parent] = value;
Congratulations, mission accomplished successfully!
There are a couple of properties I have added to the control but I didn't mention any of it here, they are too simple to be explained. I tried to comment any interesting or important point in the code example.
Points of Interest
If you take a look at the code example, you may wonder why I would add a new
EventHandler for the
Resize event of the target control?
Simply, because if you omit this, the
BalloonToolTip will not recognize any new size of your control, and will not be activated on all the control client rectangles outside its original one that it was added to with the designer. For that reason, any change in the control size must be updated with the Win32 ToolTip tool associated with that resized control, and where is it better to do that than in the
You may also note that I don't add any
EventHandler to the
MouseHover of the target controls. That's because I used the
TTF_SUBCALSS flag when I created the
TOOLINFO structure, and if you refer to the MSDN, you'll find that this flag enforces the Win32 ToolTip control itself to add the
EventHandler and perform any required housekeeping, for free!!.
I hope that this article was useful to you.