Introduction
The System.Windows.Forms
namespace includes a built-in class for displaying tooltips on a control. However, this ToolTip control does not support creating the cute balloon tooltips that are available with IE 3.0 and above. This class provides a managed wrapper for the Win32 Tooltip common control that supports displaying Balloon Tool Tips.
Using the Control
The control provides functionality exactly like the current ToolTip provider that ships with .NET. To use it on a form or control, you simply drag an instance of the control onto the form from the Visual Studio .Net toolbox. Once added, each control on the form will have a new ToolTip
property that you can set for the particular object. Visual Studio will automatically add the needed code to hook the control's tooltip to the BallonToolTip
provider.
API
The API consists of the same properties and methods as the standard ToolTip
class that the .NET Framework provides. Each instance of the BallonToolTip
class can support tool tips for several controls. To set the tooltip for a control, you use the SetToolTip
method:
public void SetToolTip(Control control, string tooltip)
Similarly, to retrieve the tooltip for a given control, use the GetToolTip
method:
public string GetToolTip(Control control)
Designer Support
Like the ToolTip
class, the BallonToolTip
class includes support for setting tooltips at design time. Adding this support for any property is relatively easy. First, the class must derive from System.ComponentModel.Component
. Second, the class must be decorated with ProvideProperty
attribute and implement the IExtenderProvider
interface. The ProvideProperty
attribute provides the designer with the name and type of the property that should be added to any control that the IExtenderProvider.CanExtend
method returns true
for. These two elements work together to support providing a ToolTip
property for each control on a form. The BallonToolTip
class is declared as follows:
[ProvideProperty("ToolTip", typeof(Control))]
public class BallonToolTip : Component, IExtenderProvider
There are a few tricks to using the ProviderProperty
attribute. For one, the class it is added to must provide both GetX
and SetX
methods, where X
is the name of the property passed to the ProvideProperty
attribute. Also, your GetX
method should be decorated with a DefaultValue
attribute to reduce the amount of code the designer has to create. In the case of the BallonToolTip
provider, the GetToolTip
method is decorated as follows:
[DefaultValue("")]
public string GetToolTip(Control control)
Annoyingly, when SetToolTip
is called from the InitializeComponent
method of the class the tooltip is present on, the controls may not yet have handles. To handle this case, the BallonToolTip
control hooks the HandleCreate
event of the Control
base class when any control is passed to SetToolTip
. When this event fires, the tooltip provider automatically updates the underlying Win32 tooltip window with the information for the newly created control. For completeness, the SetToolTip
method also hooks the HandleDestroyed
event to delete the native tooltips when a control is destroyed (in a typical form, this is not really an issue, since the control will be destroyed more or less at the same time as the provider).
Using the NativeWindow class
A little known class in the System.Windows.Forms
namespace is the NativeWindow
class. This class' one and only goal is to provide a managed wrapper around the Win32 CreateWindowEx
function. In the BallonToolTip
method, I have created a derived NativeWindow
class (NativeTooltipWindow
) that provides a few useful wrappers around this class. For example, NativeTooltipWindow
builds the CreateParams
structure used to create the native ToolTip provider without input from outside classes. This way, knowledge about how to create the window is enclosed in the class that actually does the creation (see the source code for additional comments and information).
Supporting Win9X
When sending messages to the underlying Win32 Tooltip window, some messages are required to send different values based on whether we are running on a Windows 9x machine (which uses ASCII based text) versus a NT based system (which uses Unicode). During the construction of the class, we set up the readonly
int
s to make dealing with this difference easier in the code:
private const int TTM_ADDTOOLA = 1028;
private const int TTM_ADDTOOLW = 1074;
private const int TTM_UPDATETIPTEXTA = 1036;
private const int TTM_UPDATETIPTEXTW = 1081;
private const int TTM_DELTOOLA = 1029;
private const int TTM_DELTOOLW = 1075;
private readonly int TTM_ADDTOOL;
private readonly int TTM_UPDATETIPTEXT;
private readonly int TTM_DELTOOL;
public BallonToolTip()
{
m_controls = new Hashtable();
m_active = true;
m_showAlways = false;
m_window = new NativeTooltipWindow();
if (Marshal.SystemDefaultCharSize == 1)
{
TTM_ADDTOOL = TTM_ADDTOOLA;
TTM_UPDATETIPTEXT = TTM_UPDATETIPTEXTA;
TTM_DELTOOL = TTM_DELTOOLA;
}
else
{
TTM_ADDTOOL = TTM_ADDTOOLW;
TTM_UPDATETIPTEXT = TTM_UPDATETIPTEXTW;
TTM_DELTOOL = TTM_DELTOOLW;
}
InitializeComponent();
}
In the code, we never use the constants above. Instead, we always reference the readonly
member variables to ensure we are sending the right message for the platform.
Changes
3/4/2004 - Fixed a bug in the CreateHandle
method to create the tooltip handle when it does not exist.