Windows系统托盘动画效果实现

在Windows操作系统中,系统托盘区域是用户界面的一个重要组成部分,它允许应用程序在不占用桌面空间的情况下显示状态信息或提供快速访问的入口。常见的系统托盘图标包括Windows任务管理器、Microsoft离线文件同步管理器以及许多第三方应用程序的图标。这些图标在双击时通常会打开一个对话框。如果窗口能够以类似于任务栏窗口最小化或最大化时的动画效果来显示或隐藏,那将是一种很好的用户体验。本文将展示如何实现这种动画效果。

绘制动画效果

实现最小化和最大化动画效果实际上非常简单,只需要一个函数调用即可。这个函数是DrawAnimatedRects,它用于绘制并动画化一个矩形。以下是该函数的原型:

BOOL WINAPI DrawAnimatedRects(HWND hwnd, int idAni, CONST RECT *lprcFrom, CONST RECT *lprcTo);

在动画标题时,hwnd参数是将要动画化的窗口的句柄,而不是用于剪辑的窗口,它不能为NULL。lprcFromlprcTo RECTs描述了动画的起始和结束位置。

了解如何显示动画还不够;还需要知道动画的起始和结束位置。在最小化时,“from”值很简单,可以使用窗口的RECT。这给了窗口的正确宽度,但高度将比标题栏大。这本身不是问题。已经告诉Windows正在动画化标题,所以Windows只使用标题的高度。

“to”值稍微棘手一些。需要获取系统托盘的RECT。不幸的是,没有文档化的方法可以做到这一点,可以使用的每种方法都有其缺点。获取系统托盘尺寸最准确的方式实际上是获取系统托盘的窗口。通过巧妙使用Spy++工具,可以看到系统托盘是一个类为“TrayNotifyWnd”的窗口,它是类为“Shell_TrayWnd”的顶级窗口的子窗口。可以使用FindWindow轻松获取这些窗口,然后简单地调用GetWindowRect来获取系统托盘的尺寸。由于这给出了最佳结果,这是首选方法,但必须记住这是未文档化的,并且使用了可能在未来版本的shell中不存在的窗口类名和层次结构,因此不能依赖。如果这种方法失败,退回到下一种方法。

Windows允许禁用动画(通过注册表设置或使用诸如TweakUI之类的工具),因此在调用DrawAnimatedRects之前必须检查这一点。这是一个简单的调用SystemParametersInfo,带有SPI_GETANIMATION值。如果动画已被禁用,窗口将被简单地隐藏;否则,调用DrawAnimatedRects

示例代码

本文附带的示例项目包含两个主要文件。MinimizeToTray.cpp包含最小化和恢复窗口的函数,而MinimizeDemo.cpp包含一个简单的对话框应用程序,演示如何使用这些函数。

MinimizeToTray.cpp包含两个公共函数:

VOID MinimizeWndToTray(HWND hWnd); VOID RestoreWndFromTray(HWND hWnd);

函数MinimizeWndToTray显示最小化动画并隐藏传入的窗口参数。函数RestoreWndFromTray显示恢复动画并显示传入的窗口。这两个函数都不添加或移除系统托盘图标——这留给调用者。

需要注意的一点是,如果在调用DrawAnimatedRects之前调用Shell_NotifyIcon,整个任务栏将被擦除,并且在动画完成之前不会被重新绘制。只要在动画完成后移除shell图标,任务栏就会被正确绘制。因此,在调用MinimizeWndToTrayRestoreWndFromTray后调用Shell_NotifyIcon

MinimizeDemo.cpp展示了如何在实际应用程序中使用这两个函数,甚至展示了Shell_NotifyIcon函数的简单用法。对话框响应WM_CLOSE消息和SC_MINIMIZE WM_SYSCOMMAND消息最小化到托盘,并且当图标被双击时恢复。

这段代码非常容易使用。只需将MinimizeToTray.cpp文件添加到项目中,并在应用程序中添加两个函数调用即可。源文件中有很多注释,MinimizeDemo.cpp示例非常容易理解。

这段代码是公共领域的。可以随意使用和滥用它。如果确实使用了它,会很感激给发一封简短的邮件。把它当作电子邮件软件。

后记

本文的主要目的是解释如何使用DrawAnimatedRect,并尽可能提供一种近乎万无一失的方法来找到系统托盘的位置。并没有打算描述如何使用Shell_NotifyIcon。然而,这是一个合理的地方,可以传递一些关于常见问题的提示:

通知图标最普遍的问题是,一旦为图标显示了一个菜单,点击菜单外的区域并不会关闭它。显然,“这种行为是设计使然”,并在Microsoft的知识库文章Q135788中有文档记录。

另一个经常遇到的问题是,通知图标停留在系统托盘中,当鼠标移动到它上面时会消失。这仅仅是因为程序在退出前没有移除图标——在退出前调用Shell_NotifyIcon带有NIM_DELETE将解决这个问题。

最后,有一个相当棘手的问题。当双击一个图标时,有时也可能激活另一个图标。这发生在第一个图标作为对双击消息的响应被移除时。由于这个消息是作为鼠标按钮被按下、释放然后再次按下的响应发送的,Windows仍然有一个按钮事件要发送。由于图标已经响应第一个图标被移除而移动,这个消息被发送到了它不应该去的地方。然后那个图标可能会无辜地对这个信息做出反应。

这是一个没有人可以责怪的情况。然而,可以解决这个问题。不是在双击事件上移除图标,而是在响应第二个按钮事件时移除它,一切都会完美地工作。最简单的方法是在接收到双击事件时设置一个标志,并在按钮上处理程序中标志被设置时移除图标。包含的示例代码展示了如何做到这一点。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485