气球帮助作为MessageBox()的非模态替代品






4.98/5 (60投票s)
虽然有时很有用,但用于显示信息的消息框通常很烦人。本文介绍了一种非模态的替代方案。
更新说明
本次发布的主要原因是我发现了一个关于打开位置功能的 bug:它会访问已释放的内存!此外,我还进行了一些其他更改,以方便编写自定义的 LaunchBalloon()
方法,修复了演示中的一个 bug,并添加了两个新选项。
unDELAY_CLOSE
与超时值配合使用,以延迟由其他 unCLOSE_*
选项引起的操作。这允许您无限期地保持气球显示(直到用户从咖啡休息回来等,并有时间查看它)。对于较长的超时值,我建议同时使用 unSHOW_CLOSE_BUTTON
,以便用户需要时可以快速将其关闭。
unDISABLE_XP_SHADOW
正如其名:如果设置,则不会显示 XP 对工具提示和菜单使用的那个很酷的阴影。请注意,用户也可以全局禁用阴影,在这种情况下,此选项无效。
我想添加一些新的演示,此功能可以做的比我目前演示的要多,但我认为这些 bug 修复需要尽快发布,而且我可能太懒惰以至于无法及时完成演示。所以,完成后我将再次更新。在此期间,Rama Krishna 要么已经,要么将很快发布他的 **.NET 版本**,因此将有很多气球的乐趣。 :)
CBalloonHelp 测试应用程序,在 Windows XP 下运行。
引言
使用 CBalloonHelp
API
重要事项
SetAnchorPoint()
SetBackgroundColor()
SetForegroundColor()
SetTitle()
SetContent()
SetTitleFont()
SetContentFont()
SetURL()
SetIcon()
LaunchBalloon()
特点
兼容性
内部
更新
相关文章
待办事项
谢谢
引言
我讨厌消息框。
或者更确切地说,我讨厌 MessageBox()
。某种形式的输出如此方便,只需一个函数调用,无需添加资源,无需设计界面,无需任何规划或准备——甚至不需要窗口句柄——似乎几乎迫使程序员在对用户应该知道的事情有任何疑问时都使用它。用户有可能错误地执行操作吗?最好获得确认……弹出个 MessageBox()
!我在 Windows 资源管理器中删除文件时,不得不至少点击一个 MessageBox()
。我确定吗?但是,该文件是可执行的……我真的确定吗?但是,它可能会影响已注册的程序……我真的,真的确定吗?……如果提供一个复选框允许您将来隐藏消息……但这需要额外的努力,而且有一个 **现成可用的、几乎够用的** 函数就足以了……
但更糟糕的是,有些程序觉得有必要在您执行 **您明确请求** 的操作后,通知您该操作已执行,并使用——您猜对了——MessageBox()
。一个小小的反馈没什么问题,特别是如果该操作产生了非初学者直观的效果,但以一种需要您停止正在做的事情(有时更糟的是,停止 **程序** 正在做的事情)来确认的方式来做……这不好笑。
好吧……
不过,够了,别再虚伪地抱怨了。解决方案确实存在——创建带有复选框或“全部是”/“全部否”按钮的消息框并不难。对于反馈,在状态栏中显示简短消息通常就足够了,或者可能添加一个日志窗格。但这两种选项实际上只适用于应用程序的主窗口,并且在可见性和屏幕空间方面也有其他缺点。
随着 Windows 2000 的推出,微软开始使用气球(漫画对话框式的)来显示来自系统托盘图标的消息。这似乎效果很好;当我拨入 Internet 时,连接建立后,一个小的图标会出现在托盘中,并伴随一个气球显示我的连接速度。此时使用 MessageBox()
是不可接受的,但气球很小,不显眼,并且不需要我采取任何行动。几秒钟后,它会悄然消失。Windows XP 为许多其他托盘图标提供了此功能,从系统更新到激活提醒,并通过一个关闭按钮增加了另一项功能,该按钮可以立即关闭气球,以防我对其感到厌烦而不想等待其超时。(Windows XP 还在其他情况下使用类似的气球,但这更像是 XP 模仿 MacOS 的普遍做法……)
请注意,如上所述的气球不是工具提示。工具提示(顺便说一句,托盘图标也在使用)在其当前形式下效果很好:当您将鼠标悬停在控件上时出现的小巧不显眼文本,一旦鼠标移开就会立即消失。任何使用过 MacOS 上 BalloonHelp(参考: http://developer.apple.com/techpubs/mac/HIGuidelines/HIGuidelines-240.html)的人都应该知道微软的工具提示在此用途上的优势。
使用 Internet Explorer 5 及更高版本,微软将 Balloon ToolTip 样式开放给通用使用(通过 TTS_BALLOON
样式)。但它并不容易使用,并且在没有 IE5 的系统上根本无法使用。
所以,直奔主题吧……
这一切的重点当然是我编写了一个易于使用的气球控件。我强调易于使用:为了说服自己和他人走这条路,我希望弹出帮助气球像弹出 MessageBox()
一样容易。
使用 CBalloonHelp
将 BalloonHelp.cpp 和 BalloonHelp.h 添加到您的项目中。
API
重要内容
创建气球的最简单方法是使用 LaunchBalloon() 静态函数。
void CBalloonHelp::LaunchBalloon(const CString& strTitle, const CString& strContent, const CPoint& ptAnchor, LPCTSTR szIcon /*= IDI_EXCLAMATION*/ unsigned int unOptions /*= unSHOW_CLOSE_BUTTON*/, CWnd* pParentWnd /*= NULL*/, const CString strURL /*= ""*/, unsigned int unTimeout /*= 10000*/)
这将分配一个新的 CBalloonHelp
对象,创建窗口并显示它。窗口关闭时,CBalloonHelp
对象将自动删除。参数如下:
strTitle // Title of balloon strContent // Content of balloon ptAnchor // point tail of balloon will be "anchor"ed to. // This is in client coordinates if pParentWnd is given, // otherwise it is in screen coordinates. szIcon // One of: // IDI_APPLICATION // IDI_INFORMATION IDI_ASTERISK (same) // IDI_ERROR IDI_HAND (same) // IDI_EXCLAMATION IDI_WARNING (same) // IDI_QUESTION // IDI_WINLOGO unOptions /* One or more of: unCLOSE_ON_LBUTTON_DOWN | closes window on WM_LBUTTON_DOWN unCLOSE_ON_MBUTTON_DOWN | closes window on WM_MBUTTON_DOWN unCLOSE_ON_RBUTTON_DOWN | closes window on WM_RBUTTON_DOWN unCLOSE_ON_LBUTTON_UP | closes window on WM_LBUTTON_UP unCLOSE_ON_MBUTTON_UP | closes window on WM_MBUTTON_UP unCLOSE_ON_RBUTTON_UP | closes window on WM_RBUTTON_UP unCLOSE_ON_MOUSE_MOVE | closes window when user moves mouse | past threshhold unCLOSE_ON_KEYPRESS | closes window on the next keypress message sent to this thread. unCLOSE_ON_ANYTHING | all of the above. unDELAY_CLOSE | when a user action triggers the close, | begins timer. closes when timer expires. unSHOW_CLOSE_BUTTON | shows close button in upper right unSHOW_INNER_SHADOW | draw inner shadow in balloon unSHOW_TOPMOST | place balloon above all other windows unDISABLE_XP_SHADOW | disable Windows XP's drop-shadow effect | (overrides system and user settings) unDISABLE_FADE | disable the fade-in/fade-out effects | (overrides system and user settings) unDISABLE_FADEIN | disable the fade-in effect unDISABLE_FADEOUT | disable the fade-out effect */ pParentWnd // Parent window/anchor window. If NULL, balloon will be // anchored in screen coordinates, and owned by the // application's main window. strURL // If not empty, when the balloon is clicked ShellExecute() // will be called, with strURL passed in. unTimeout // If not 0, balloon will automatically close after unTimeout // milliseconds.
用途
CBalloonHelp::LaunchBalloon("BoogaBooga", "What the hell is \"Booga Booga\" supposed to mean, anyway?", CPoint(0,0)); CBalloonHelp::LaunchBalloon("You are holding down the right mouse button!", "Blah", Cpoint(0,0), IDI_WARNING, CballoonHelp::unCLOSE_ON_RBUTTON_UP|CBalloonHelp::unSHOW_INNER_SHADOW, this, "", 0);
上面的第一行将显示一个带有标题“BoogaBooga”和相关消息的气球,该气球固定在屏幕的左上角(点是屏幕坐标)。理想情况下,您会将其固定到更有意义的内容上,例如控件的中心或状态栏图标。默认选项将导致此气球在 10 秒后消失,并在左上角显示标准信息图标,在右上角显示关闭按钮。
上面的第二行将显示一个气球,这次固定在由 this
表示的窗口的左上角,这次带有标准警告图标。CBalloonHelp::unCLOSE_ON_RBUTTON_UP
选项将导致它在释放鼠标右键时销毁——如果按住鼠标按钮,则会捕获鼠标输入,因此气球将在释放鼠标右键时关闭。如果未按住鼠标按钮,用户必须在同一线程拥有的窗口内的某个位置释放鼠标右键才能关闭。CballoonHelp::unSHOW_INNER_SHADOW
选项将导致气球以内部高光和阴影绘制……不是很有趣,但如果您不在 Windows XP 上运行,这是您唯一能获得的阴影。
也可以使用 Create
函数创建气球,在这种情况下,您可以选择不让对象在窗口关闭时自动删除(LaunchBalloon
函数始终添加 CBalloonHelp::unDELETE_THIS_ON_CLOSE
选项强制执行此操作,但这可能不是期望的行为,例如,如果您从堆栈中分配了一个 CBalloonHelp
对象)。此函数还提供了更大的自定义潜力(更多信息请参见下文)。
完整的 API
BOOL CBalloonHelp::Create(const CString& strTitle, const CString& strContent, const CPoint& ptAnchor, unsigned int unOptions, CWnd* pParentWnd /*=NULL*/, const CString strURL /*= ""*/, unsigned int unTimeout /*= 0*/, HICON hIcon /*= NULL*/);
这将创建并显示一个气球窗口。标题和内容将覆盖在此调用之前为对象设置的任何内容。ptAnchor
表示屏幕坐标中的锚点位置。unOptions
应是一个或多个以下选项的组合:
CBalloonHelp::unCLOSE_ON_LBUTTON_DOWN // closes window on WM_LBUTTON_DOWN CBalloonHelp::unCLOSE_ON_MBUTTON_DOWN // closes window on WM_MBUTTON_DOWN CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN // closes window on WM_RBUTTON_DOWN CBalloonHelp::unCLOSE_ON_LBUTTON_UP // closes window on WM_LBUTTON_UP CBalloonHelp::unCLOSE_ON_MBUTTON_UP // closes window on WM_MBUTTON_UP CBalloonHelp::unCLOSE_ON_RBUTTON_UP // closes window on WM_RBUTTON_UP CBalloonHelp::unCLOSE_ON_RBUTTON_UP // closes window on WM_RBUTTON_UP CBalloonHelp::unCLOSE_ON_MOUSE_MOVE // closes window when user moves mouse past // threshhold CBalloonHelp::unCLOSE_ON_KEYPRESS // closes window on the next keypress message sent // to this thread. CBalloonHelp::unCLOSE_ON_ANYTHING; // all of the above CBalloonHelp::unDELAY_CLOSE; // when a user action triggers the close, // begins timer. closes when timer expires. CBalloonHelp::unDELETE_THIS_ON_CLOSE // deletes object when window is closed. Used by // LaunchBalloon(), use with care CBalloonHelp::unSHOW_CLOSE_BUTTON // shows close button in upper right CBalloonHelp::unSHOW_INNER_SHADOW // draw inner shadow in balloon CBalloonHelp::unSHOW_TOPMOST // place balloon above all other windows CBalloonHelp::unDISABLE_XP_SHADOW; // disable Windows XP's drop-shadow effect // (overrides system and user settings) CBalloonHelp::unDISABLE_FADE // disable the fade-in/fade-out effects // (overrides system and user settings) CBalloonHelp::unDISABLE_FADEIN // disable the fade-in effect CBalloonHelp::unDISABLE_FADEOUT // disable the fade-out effect
如果 pParentWnd
为 NULL
,气球将以屏幕坐标固定,并由应用程序的主窗口(AfxGetMainWnd())
拥有。如果没有主窗口,并且 pParentWnd
为 NULL
,则创建将失败。如果 strURL
非空,则在单击气球时将其传递给 ShellExecute()
。如果 unTimeout
不为 0,则指定气球自动关闭的毫秒数。如果 hIcon
不为 NULL,则指定气球左上角显示的图标;它会在创建时被复制,因此图标可以在 Create()
返回后安全销毁。
void CBalloonHelp::SetAnchorPoint(CPoint ptAnchor, CWnd* pWndAnchor = NULL);
设置气球固定的点(气球尾巴附加的点)。在气球创建之前调用此函数无效,因为锚点是 Create()
的必需参数。如果 pWndAnchor
为 NULL
,则 ptAnchor
被假定为屏幕坐标;否则,它被假定为相对于 pWndAnchor
的客户端区域。
void CBalloonHelp::SetBackgroundColor(COLORREF crBackground);
设置气球的背景颜色。可以在气球创建之前或之后调用。
void CBalloonHelp::SetForegroundColor(COLORREF crForeground);
设置气球的前景(边框和文本)颜色。可以在气球创建之前或之后调用。
void CBalloonHelp::SetTitle(const CString& strTitle);
设置气球的标题。可以在气球创建之前或之后调用。
void CBalloonHelp::SetContent(const CString& strContent);
设置气球的内容。可以在气球创建之前或之后调用。
void CBalloonHelp::SetTitleFont(CFont* pFont);
设置用于绘制气球标题的字体。可以在气球创建之前或之后调用。字体和 CFont 对象由气球存储并最终删除;调用此函数后请勿使用其中任何一个。
void CBalloonHelp::SetContentFont(CFont* pFont);
设置用于绘制气球内容的字体。可以在气球创建之前或之后调用。如果在创建之前调用此函数,并且没有显式设置标题字体(通过 SetTitleFont()
),则标题将使用此字体的粗体版本(即使此字体本身已经是粗体)。字体和 CFont 对象由气球存储并最终删除;调用此函数后请勿使用其中任何一个。
void CBalloonHelp::SetURL(const CString& strURL);
设置气球单击时要打开的 URL 或文件。设置为“ ”以禁用。可以在气球创建之前或之后调用。
void CBalloonHelp::SetIcon(HICON hIcon);
设置显示在气球左上角的图标。传递 NULL 表示不显示图标。图标不会被缩放。可以在气球创建之前或之后调用。
void CBalloonHelp::SetIcon(HBITMAP hBitmap, COLORREF crMask);
设置显示在气球左上角的图标。crMask
表示透明颜色。传递 NULL 表示 hBitmap
不显示图标。图标不会被缩放。可以在气球创建之前或之后调用。
void CBalloonHelp::SetIcon(HBITMAP hBitmap, HBITMAP hMask);
设置显示在气球左上角的图标。hMask
表示透明区域。两个参数都必须有效。图标不会被缩放。可以在气球创建之前或之后调用。
void CBalloonHelp::SetIcon(CImageList* pImageList, int nIconIndex);
设置显示在气球左上角的图标。nIconIndex
表示要使用的图像。两个参数都必须有效。图标不会被缩放。可以在气球创建之前或之后调用。
void CBalloonHelp::LaunchBalloon(const CString& strTitle, const CString& strContent, const CPoint& ptAnchor, LPCTSTR szIcon /*= IDI_EXCLAMATION*/, unsigned int unOptions /*= unSHOW_CLOSE_BUTTON*/, CWnd* pParentWnd /*= NULL*/, const CString strURL /*= ""*/, unsigned int unTimeout /*= 10000*/)
创建并显示一个气球。这是一个静态函数,因此在 CBalloonHelp 实例上调用它不太有用。如果需要更多控制,请创建派生类,或使用 Create()
和相关的属性函数。有关更多信息,请参阅 用法。
特点
- 易于使用的界面。
- 根据锚点位置调整位置;窗口将始终完全可见,前提是内容大小小于屏幕大小。
- 如果可用,则使用半透明效果。
- 如果可用,则使用阴影效果。
兼容性
已在 Windows XP、2000、98 和 95 上测试过。
使用 VC++ 6.0 / MFC 6 和 VC++.NET / MFC 7 编译。
内部
CBalloonHelp
是一个 MFC 类,派生自 CWnd
。第一次创建气球时,会注册一个名为“BalloonHelpClass”的窗口类。首先,注册一个带有 CS_DROPSHADOW
样式的窗口类;如果注册失败,则删除该样式,然后再次尝试注册。这允许在 Windows XP 上使用该样式,同时避免在早期 Windows 版本中出现问题。
窗口创建为隐藏状态,然后计算内容和标题的大小。在此基础上加上边距和尾部空间,然后根据锚点调整窗口大小和位置。最后,设置窗口区域并显示窗口。
提示:在制作异形窗口时,将异形部分放在非客户区;如果您在绘制客户区时不必考虑这些,那会 **容易得多**。
如果可用且已请求,则使用 AnimateWindow()
进行淡入/淡出效果。
使用消息钩子来确定何时发生关闭鼠标操作。
Andrew Nosenko 的 CAuxThunk
实现(重命名为 _ThunkImpl
)用于实现消息钩子。
更新
8/2/02
- 修复了演示中的 bug,该 bug 会在错误的位置显示除以零的气球。
- 修复了在使用 strURL 参数在气球被点击时打开文件或位置时访问已释放内存的 bug。
- 添加了选项:
CBalloonHelp::unDELAY_CLOSE
。 - 添加了选项:
CBalloonHelp::unDISABLE_XP_SHADOW
。
5/30/02
- 发布了 Maximilian Hänel 的 WTL 移植版。
- 版本 #2(任意数字,但需要一个)
- 增加了对多显示器的支持
- 增加了对 WM_*BUTTON_DOWN 消息关闭的支持。
- 增加了对固定到窗口的支持(随父窗口移动)。
- 重构了消息钩子代码,大部分取自 Max 的 WTL 移植版。
1/23/02
- 发布了 Fil Mackay 的 ATL 移植版。
12/31/01
- 再次更改了透明度使用方式,希望这将解决 Win2k 的问题。
- 略微调整了边框计算代码。
- 修复了无标题会导致气球全屏显示的 bug。
12/21/01
- 扩展了 API 以允许更大的自定义。
- 添加了气球手动创建并随父窗口移动的示例。
- 实现了键盘钩子以允许按键关闭。
- 单独的标志用于禁用淡入/淡出。
- 对 LaunchBalloon() 使用的小图标进行了平滑缩放(仅限 Win2k/XP)。
- 杂项代码清理。
12/12/01
- 修复了鼠标在淡出完成之前未释放的 bug。
- 添加了禁用淡入效果的选项;如果用户通过控制面板禁用了它们,则强制启用此选项。
- 添加了将气球设置为最顶层窗口的选项。
相关文章
- CXInfoTip - 信息工具提示 Mark Bozeman
- 消息气球 Prateek Kaul
- ClassLib, 一个 C++ 类库 Jan van den Baard
- 在系统托盘中添加图标 Chris Maunder(这篇讨论的是另一个主题,但使用了 Windows 2000 及更高版本可用的 Windows Shell 的工具提示功能)
- RGN 生成器 Richard de Oude(关于使用区域创建异形窗口的信息)
-
在窗口中创建孔 Amir Salzbe(与上面相同,但更简单。)
待办事项
稍微清理一下代码……大部分都很直接,但文档可以更好。
迁移到纯 Win32,而不是 MFC 派生类。
为本文添加更好的代码演练。
感谢……
……Jan van den Baard 告诉我使用 AnimateWindow() 的正确方法,并演示了如何使用 WM_NCHITTEST
来提供热跟踪。查看他在 CP 上的 ClassLib 库!
……Maximilian Hänel 的 WTL 移植版,以及他在其中演示的处理消息钩子的更好方法。
……所有提供反馈的人,无论好坏。这些都有帮助。
……Mustafa Demirhan,感谢他关于使用键盘钩子的建议和信息。
……The Code Project,为我们所有人提供了一个可用的论坛。