构建 C# 中的气球工具提示提供程序
演示了如何创建支持气球形工具提示的 ToolTip 提供程序,包括创建扩展属性和使用 NativeWindow 类相关的问题。
引言
System.Windows.Forms
命名空间包含一个内置类,用于在控件上显示工具提示。但是,此 ToolTip 控件不支持创建 IE 3.0 及更高版本中提供的可爱气球形工具提示。此类提供了一个托管包装器,用于支持显示气球形工具提示的 Win32 Tooltip 公共控件。
使用控件
该控件提供与 .NET 附带的当前 ToolTip 提供程序完全相同的功能。要在窗体或控件上使用它,只需将控件的一个实例从 Visual Studio .Net 工具箱拖到窗体上。添加后,窗体上的每个控件将拥有一个新的 ToolTip
属性,您可以为特定对象设置该属性。Visual Studio 将自动添加必要的代码,将控件的工具提示连接到 BallonToolTip
提供程序。
API
API 由 .NET Framework 提供的标准 ToolTip
类具有相同的属性和方法。BallonToolTip
类的每个实例都可以支持多个控件的工具提示。要为控件设置工具提示,请使用 SetToolTip
方法。
public void SetToolTip(Control control, string tooltip)
同样,要检索给定控件的工具提示,请使用 GetToolTip
方法。
public string GetToolTip(Control control)
设计器支持
与 ToolTip
类一样,BallonToolTip
类支持在设计时设置工具提示。为任何属性添加此支持相对容易。首先,类必须派生自 System.ComponentModel.Component
。其次,类必须用 ProvideProperty
属性进行修饰,并实现 IExtenderProvider
接口。ProvideProperty
属性向设计器提供应添加到 IExtenderProvider.CanExtend
方法返回 true
的任何控件的属性的名称和类型。这两个元素协同工作,以支持为窗体上的每个控件提供 ToolTip
属性。BallonToolTip
类的声明如下:
[ProvideProperty("ToolTip", typeof(Control))]
public class BallonToolTip : Component, IExtenderProvider
使用 ProviderProperty
属性有一些技巧。一方面,它被添加到的类必须同时提供 GetX
和 SetX
方法,其中 X
是传递给 ProvideProperty
属性的属性的名称。此外,您的 GetX
方法应使用 DefaultValue
属性进行修饰,以减少设计器需要创建的代码量。对于 BallonToolTip
提供程序,GetToolTip
方法的修饰如下:
[DefaultValue("")]
public string GetToolTip(Control control)
令人烦恼的是,当 SetToolTip
方法在具有工具提示的类的 InitializeComponent
方法中被调用时,控件可能尚未创建句柄。为了处理这种情况,当任何控件被传递给 SetToolTip
时,BallonToolTip
控件会挂钩 Control
基类的 HandleCreate
事件。当此事件触发时,工具提示提供程序会自动使用新创建控件的信息更新底层的 Win32 工具提示窗口。为了完整起见,SetToolTip
方法还挂钩 HandleDestroyed
事件,以便在控件被销毁时删除本机工具提示(在典型的窗体中,这通常不是问题,因为控件的销毁时间与提供程序的销毁时间大致相同)。
使用 NativeWindow 类
System.Windows.Forms
命名空间中一个鲜为人知的类是 NativeWindow
类。这个类唯一的目的是提供 Win32 CreateWindowEx
函数的托管包装器。在 BallonToolTip
方法中,我创建了一个派生的 NativeWindow
类(NativeTooltipWindow
),它提供了一些围绕此类的有用包装器。例如,NativeTooltipWindow
构建了 CreateParams
结构,用于在不接受外部类输入的情况下创建本机 ToolTip 提供程序。这样,关于如何创建窗口的知识就被封装在了实际执行创建的类中(有关其他注释和信息,请参阅源代码)。
支持 Win9X
在向底层 Win32 Tooltip 窗口发送消息时,某些消息需要根据我们是在 Windows 9x 计算机(使用基于 ASCII 的文本)还是 NT 系统(使用 Unicode)上运行来发送不同的值。在类构造过程中,我们设置了 readonly
int
s,以便在代码中更轻松地处理这种差异。
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;
/// <SUMMARY>
/// Initializes a new instance of the
/// <SEE cref="ToolTipLibrary.BallonToolTip" /> class.
/// </SUMMARY>
public BallonToolTip()
{
m_controls = new Hashtable();
m_active = true;
m_showAlways = false;
//Create a new native window.
m_window = new NativeTooltipWindow();
if (Marshal.SystemDefaultCharSize == 1)
{
//Win9x machines
TTM_ADDTOOL = TTM_ADDTOOLA;
TTM_UPDATETIPTEXT = TTM_UPDATETIPTEXTA;
TTM_DELTOOL = TTM_DELTOOLA;
}
else
{
//WinNT machines
TTM_ADDTOOL = TTM_ADDTOOLW;
TTM_UPDATETIPTEXT = TTM_UPDATETIPTEXTW;
TTM_DELTOOL = TTM_DELTOOLW;
}
InitializeComponent();
}
在代码中,我们从不使用上述常量。相反,我们始终引用 readonly
成员变量,以确保我们为平台发送了正确的消息。
变更
2004 年 3 月 4 日 - 修复了 CreateHandle
方法中的一个错误,该错误在工具提示句柄不存在时创建它。