65.9K
CodeProject 正在变化。 阅读更多。
Home

BalloonTip 显示

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (9投票s)

2005年5月23日

CPOL

9分钟阅读

viewsIcon

98266

downloadIcon

4190

一篇关于气球提示的文章。

背景

警告!!

为了很好地理解本文及其源代码,您应该具备一些 WinAPI 函数、常量和结构体的知识。如果您不明白我在说什么,将很难自行弄清楚。查看“关注点”部分,了解您可以在哪里开始搜索相关知识。

最初...

最近,我一直在开发一个 Felix the Cat 桌面伴侣。当我想让 Felix 与用户“交流”时,我对卡通风格的气球工具提示产生了兴趣。由于 C# 没有气球控件类,但我真的很想让 Felix “说话”,于是我开始研究如何实现这一点。我知道至少有两种方法可以遵循:

  • 第一种是使用 GDI+ 实现一个气球类;
  • 第二种是查看 Windows 使用什么来显示它的气球,看看我是否也能使用它。

这两条路对我来说都充满未知。

  • 我对 GDI+ 有些了解,但不够深入。我也知道区域和图形需要消耗大量处理器资源,而且搜索结果中没有小型的源代码,最重要的是,它看起来很难掌握。这并不是说我害怕或者什么的,我只是不想为了一个该死的气球而深入研究 GDI+。顺便说一句,GDI+ 是一个很棒的工具,相信我,一旦你掌握了它,你就能做出一些非常酷的东西。
  • 我也知道 WinAPI 是什么,它能做什么,以及它有多强大。所以,我毫不怀疑通过它有一种更简单的方法来获得气球。

基于这些事实,我冒险尝试了 WinAPI。

然后,我就看到了曙光……

我首先查找了 CodeProject,看看是否有人实现了相关代码。我发现了两篇文章,一篇是用 GDI+ 实现的,另一篇是使用 WinAPI 完成的。

我下载了 WinAPI 的演示和代码,以为我的问题就解决了。

看来并非如此……

像任何好奇的人一样,我首先运行了演示,开始点击这里那里的按钮,我还阅读了窗体上的文字,希望能弄清楚程序的作用。我的点击徒劳无功;在众多按钮和三个窗体中,只有一个显示了气球工具提示,并将其一直停留在屏幕上直到应用程序结束,让用户知道存在糟糕的资源管理。之后,我决定查看代码;代码只有很少的注释,有几个无用的类,它们要么不能按预期工作,要么根本不起作用,最重要的是,我不明白作者做了什么,为什么或如何做的,换句话说,没有明确的思路如何或从哪里开始。我**不**高兴。

我决定看看 GDI+ 解决方案,于是我下载并运行了它的演示项目。这次我惊喜地发现。有人制作了一个很酷的多功能气球工具提示,实际上太酷了,因此也太复杂了。它有很多我不需要的额外的东西,我不想花时间去修改代码,而且我对 GDI+ 并不太熟悉。当时我唯一的解决方案是使用 WinAPI 做一些更好的东西。

当有人做得更好时,总有一个起点,我的起点和我失望的那个项目一样,就是那个《Ballons Tips Galore》文章。这并不意味着我偷了他的代码,一点也不是,你可以自己看看实现是不同的。例如,为了创建气球窗口,他使用了 NativeWindow 类,而我使用了 CreateWindowEx WinAPI 函数。所以不,这甚至不是重制,我只是用代码来了解气球工具提示窗口是如何创建的,以及我可以在哪里开始我的搜索。

问题是,每当我想要用 WinAPI 做一些新事情时,我都找不到 MSDN 中的任何真正帮助;是的,常量名称在那里,但没有实际值;是的,API 已解释,但没有任何有用的代码显示如何将它们整合在一起。所以你看,对我来说唯一有效的方法就是看别人的代码并从中学习。这就是为什么我的源代码可用并且有充分文档的原因,这样任何人都可以从中学习,理解什么、为什么以及如何完成。

我的起点...

MSDN 有一个搜索引擎。我输入 tooltip,没有过滤器,出现了 500 多个结果;我输入 Tooltip controls,再次出现 500 多个结果,但第一个是完全匹配的。是“Windows Shell and Controls”部分中的“Tooltip Controls”。

我开始阅读,惊讶地发现了很多关于如何使用同一个控件类创建和显示不同类型工具提示的信息。我将简要解释我发现的和理解的内容。

严格来说,气球工具提示是一个窗口,当然不是我们通常认识的普通窗口,而是从一个窗口类声明出来的窗口,是不是有点令人困惑?我们一步一步来。

如前所述,本演示中生成的气球工具提示是通过调用 CreateWindowEx WinAPI 函数创建的。

根据 MSDN

CreateWindowEx 函数创建一个具有扩展窗口样式的重叠窗口、弹出窗口或子窗口。

正如你所见,它接收很多参数。

//MSDN Sintax:
HWND CreateWindowEx( 
    DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, 
    DWORD dwStyle, int x, int y, int nWidth, 
    int nHeight, HWND hWndParent, HMENU hMenu, 
    HINSTANCE hInstance, LPVOID lpParam
);

----------------
//C# Declaration (only in this demo)
[DllImport("User32", SetLastError=true)]
internal static extern int CreateWindowEx (
    int dwExStyle, string lpClassName, string lpWindowName, int dwStyle, 
    int X, int Y, int nWidth, int nHeight, int hWndParent, int hMenu,
    int hInstance, IntPtr lpParam);

----------------
//Direct Use (only in this demo)
m_lTTHwnd = CreateWindowEx(0, TOOLTIP_CLASS, string.Empty, 
   lWinStyle, 0, 0, 0, 0, m_lParentHwnd, 0, 0, IntPtr.Zero);

每个参数都有自己的描述(请参阅 MSDN),这次我们只关注第二个参数,即 LPCTSTR lpClassName

根据 MSDN 的描述:

lpClassName 必须是指向以 null 结尾的字符串的指针,或者是通过先前调用 RegisterClassRegisterClassEx 函数创建的类原子。原子必须位于 lpClassName 的低位字中;高位字必须为零。如果 lpClassName **是一个字符串**,则它指定窗口类名。类名可以是与 RegisterClassRegisterClassEx 注册的任何名称,前提是注册该类的模块也是创建该窗口的模块。类名也可以是任何预定义的系统类名。

忽略原子部分,再次阅读字符串部分……这意味着 CreateWindowEx 可以创建不同窗口类的句柄。有三种类型的窗口类:

  • 系统类
  • 应用程序全局类
  • 应用程序本地类

这些类型在作用域以及何时何地注册和销毁它们方面有所不同。从 MSDN 继续阅读,我发现 Button、ComboBox、ListBox 等控件是系统类。这清楚地表明存在一个 ToolTip 类,它被称为“TOOLTIPS_CLASS”。

正如 CreateWindowEx 函数明确说明的那样,这个类必须被注册,当通用控件动态链接库(Comctl32.dll)加载时就会进行注册。

如果函数成功,返回值是新工具提示的句柄。通过这个唯一的数字,您可以使用窗口消息与气球进行通信并对其进行配置。

气球显示……进行中

通信是通过 Windows Procedures 的存在来实现的。每个窗口都有一个关联的函数,用于处理发送或张贴给该类所有窗口的所有消息。窗口外观和行为的所有方面都取决于窗口过程对这些消息的响应,气球工具提示窗口也不例外。

这些消息通常由系统发送,但用户可以使用 SendMessage 函数来调用指定窗口的窗口过程。

//MSDN Sintax:
LRESULT SendMessage(    
    HWND hWnd, UINT Msg,
    WPARAM wParam, LPARAM lParam
);
  • hWnd - 是将接收消息的窗口的句柄。
  • Msg - 指定要发送的消息。
  • wParam - 指定附加的消息特定信息。
  • lParam - 指定附加的消息特定信息。

返回值指定处理后消息的结果,并取决于发送的消息。

所有可以发送给 Tooltip 的消息都在“CommCtrl.h”中声明并可以找到。这次,我只打印出其中一些。

#define TTM_ACTIVATE            (WM_USER + 1)
#define TTM_SETDELAYTIME        (WM_USER + 3)
#define TTM_ADDTOOLA            (WM_USER + 4)
#define TTM_ADDTOOLW            (WM_USER + 50)
#define TTM_DELTOOLA            (WM_USER + 5)
#define TTM_DELTOOLW            (WM_USER + 51)
#define TTM_NEWTOOLRECTA        (WM_USER + 6)
#define TTM_NEWTOOLRECTW        (WM_USER + 52)
#define TTM_RELAYEVENT          (WM_USER + 7)
//* WM_USER is just a constant. It's value is 0x400.
//* Messages ranging from [WM_USER-0x7FFF] are for use
//  by private window classes, like the ToolTip class.

正如 SendMessage 函数明确指出的那样,每个消息都有其自己的参数,TTMessage 也是如此。该项目向您展示了如何使用其中大部分,甚至更多,它向您展示了如何使用 C# 来处理它们。

缺点

我发现了一些您可能不愿使用气球提示的原因,我认为公平地告知您它们是明智的。

  • 只能显示三种图标: 图标。
  • 如果没有选择标题,则不显示图标。
  • 通过代码将它们放置在屏幕上的特定位置可能有些困难。
  • 您无法选择提示的位置,通常它会向上显示。提示的位置在气球显示时计算。它取决于气球屏幕的位置。我找到了一种使用 MoveWindow WinAPI 函数在任何地方向下显示气球的方法。

关注点

为了很好地理解本文及其源代码,您应该具备一些 WinAPI 函数、常量和结构体的知识。如果您对这些都不熟悉,CodeProject 上有一些文章,网络上也有大量信息。我推荐两个网站,它们托管的应用程序在我开发 WinAPI 相关内容时非常有帮助。

结束语

本文仅描述了气球工具提示控件的一小部分,提供的源代码将帮助您更好地理解它,因此请毫不犹豫地下载并学习。总有更多可以做的,MSDN 提供了信息,以及像您和我这样的用户在网络上提供了很多帮助,您只需要深入研究提供的帮助和代码,找到自己的出路。没人说这很容易或很快,本文(包括项目)花了我很多时间才运行起来,但最终,我们都赢了。

别忘了,任何评论、批评、问题、错误或任何事情都随时欢迎。

© . All rights reserved.