CBalloonMsg - AfxMessageBox 的一个易于使用的非模态替代品






4.71/5 (27投票s)
轻松使用气球提示框非模态地传达提示/消息

引言
在开发一个新应用程序时,我希望使用气球风格的工具提示来向用户传达简单的消息,而不是依赖传统的 ::AfxMessageBox
。这样可以减少对用户输入错误的警告,例如编辑错误、未填写字段等,显得不那么干扰。
我找到了一篇 CodeProject 的现有文章,似乎正是我想要的:《气球帮助作为 MessageBox() 的非模态替代品》。然而,仔细检查后,我决定不使用它,因为它没有使用 Windows 原生的气球提示。它自己绘制气球,而且在我撰写本文时,我认为它需要为 Vista 进行一些小更新。肯定有更简单的方法可以使用真正的工具提示控件吧?我没有找到,于是我写了自己的,叫做 CBalloonMsg
。
必备组件
在继续之前,请注意,我编写此代码是为了在主题感知型应用程序(即具有请求公共控件 v6 的清单的应用程序)中运行,这些应用程序运行在 Vista 或 XP(最好是 SP2)上。我没有尝试在 Windows 2000 上使用它,但我很确定它看起来不会对。更不用说 NT4 和 Win9x 了!
Using the Code
这再简单不过了
- 将 BalloonMsg.h 和 BalloonMsg.cpp 添加到您的项目中。
- 确保您的 stdafx.h 将
WINVER
和_WIN32_WINNT
设置为0x0501
或更高版本,并确保您的 *.rc2 文件中有一个清单(请查看演示项目!) - 最后,使用
Show
或ShowForCtrl static
方法之一来显示气球消息,例如:
CBalloonMsg::ShowForCtrl( _T("Test Title"), _T("Test Text"), &my_ctrl, hIcon );
Show
/ShowForCtrl
方法会在指定的窗口位置或控件上方显示工具提示。默认情况下,工具提示会显示 10 秒钟然后自动关闭。如果有焦点变化或鼠标明显移动,它会提前关闭。您可以轻松更改“显示时长”或自动弹出时间(稍后会详细介绍),并且可以使用 RequestCloseAll() static
方法随时关闭或“弹出”气球。
它是如何工作的(简而言之)
当您调用 Show
方法之一时,会创建一个单独的用户界面线程。该线程反过来会在当前鼠标位置创建一个自己的小型透明窗口,然后开始处理该窗口的消息队列。该窗口充当工具提示控件的父窗口,并调用 CToolTipCtrl::RelayEvent
以确保工具提示优先处理所有相关的 Windows 消息。工具提示的初始延迟为零毫秒,因此一旦激活就会立即显示。当工具提示最终关闭时,会调用 PostQuitMessage
,该函数会销毁透明父窗口并终止线程。包装器线程会自行删除以确保完整性。
大致就是这样。您可以在代码的注释中找到有关内部机制的更详细信息。
关注点
SafeShowMsg 和 BalloonsEnabled
默认情况下,Windows 允许使用气球工具提示。但是,有一个注册表修改可以阻止气球的显示。这通常用于禁用 Windows 偶尔弹出的那些冗余且分散注意力的托盘气球。
该修改涉及在 HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced 下将 DWORD
值 EnableBalloonTips
设置为零(感谢 DerMeister 的报告!)。
为了解决这个问题,最新版本的类现在包括了两个额外的方法:BalloonsEnabled
和 SafeShowMsg
。
BalloonsEnabled()
如果用户没有禁用气球工具提示,则返回 TRUE
。在此基础上,SafeShowMsg()
会在启用了气球的情况下使用气球显示消息,并在用户关闭气球的情况下回退到 AfxMessageBox
。演示项目展示了此函数的功能 - 请查看 CTTTestDlg::DoDataExchange
。
图标
您可以为 Show
调用提供自己的图标,或者使用 Windows 内置图标,方法是使用 MSDN 文档中关于 TTM_SETTITLE 的特殊值:1 表示信息,2 表示警告,3 表示错误。
您还可以通过设置其 static
成员的新值来更改 CMessageBalloon
的许多默认值
s_nTimerStep |
默认为 30 毫秒。确定检查气球终止状态的频率(请参阅版本 2 的历史记录更改)。 |
s_nAutoPop |
气球自动关闭之前的时间(以毫秒为单位)。默认为 10 秒。设置为 0 表示气球将一直显示,直到通过焦点更改、用户操作等方式关闭。 |
s_nMaxTipWidth |
最大提示宽度(以像素为单位)。使气球使用换行。 |
s_nToolBorder |
鼠标移动多少像素距离后气球才会弹出 |
GetGUIThreadInfo
最后,请注意使用了方便的函数 GetGUIThreadInfo
,它允许我们在另一个线程中检查焦点窗口。
Damir Valiulin 适配的 VC6 特殊版本
文章顶部的第三个下载是 VC6 版本的演示项目。它包含与原始代码的一些细微差别,如下所示:
- 为能在 VC6 下编译进行的小修改
- 检查 Win32(没有气球提示)和注册表禁用技巧
- 对函数调用进行了简化(移除了带有
string
ID 的调用)
非常感谢 Damir 的贡献!
历史
版本 1:2008 年 3 月 6 日
版本 2:2008 年 3 月 16 日
改为使用跟踪工具提示,以对抗当对话框靠近屏幕边缘时气球及其指针定位的异常。使用 TTF_TRACK
需要其他更改。
- 现在通过
TTM_TRACKPOSITION
消息重新定位气球,而不是使用SetWindowPos
。 - 现在使用
TTM_TRACKACTIVATE
来激活工具提示,而不是 MFC 的Activate()
方法。 - 还创建了一个定时器,设置为每大约 30 毫秒触发一次(可通过上面的
s_nTimerStep
配置)。定时器使我们有机会发现主线程拥有的窗口状态的变化,这些变化应导致气球关闭。它还允许我们在s_nAutoPop
不为零时实现自动关闭间隔(自动弹出)。
版本 3:2008 年 3 月 23 日
- 添加了
SafeShowMsg
和BalloonsEnabled
,用于检测用户是否通过注册表修改禁用了气球,并回退到AfxMessageBox
。 - 添加了重载,以同时接受用于目标控件的
HWND
和CWnd*
。
版本 4:2008 年 3 月 30 日
- 添加了 VC6 版本,由 Damir Valiulin 亲切适配。