自动等待光标






4.25/5 (14投票s)
2002 年 1 月 1 日
4分钟阅读

127678

1467
自动将鼠标光标更改为沙漏等待光标。
引言
建议的做法是将鼠标光标更改为沙漏等待光标,以告知用户程序正在处理某些事情。当程序员预先知道某件事需要一段时间时,这样做很容易。然而,在很多情况下,你无法预测某件事要花多长时间,因此很难知道是否要显示沙漏。理想情况下,你希望光标在你的任务运行超过一定时间限制(例如 1/10 秒)后自动更改为沙漏。我需要为我的一个项目这样做,所以我搜索了我的书和互联网,但找不到任何适用的代码。我能找到的最接近的东西是一篇文章,解释了如何在 Java 中实现它。 它似乎不难编写一个 Windows/C++ 版本,但它最终花费的时间比我预期的要长才能正常工作,所以我想分享一下结果。
实现
我的基本想法是创建一个辅助线程,充当“计时器”。消息循环将不断重置计时器,使其通常不会用完。但是,如果任务花费的时间更长,那么计时器将会用完,并且辅助线程会将光标更改为沙漏。设置这个花不了多长时间,但令人费解的是,它不起作用。一点调试让我确信我的代码运行正常,但辅助线程中的 SetCursor
调用不起作用。我猜测 SetCursor
无法从辅助线程工作,但在 MSDN 文档和互联网上搜索并没有找到任何关于此的内容。最后,我在comp.os.ms-windows.programmer.win32 上发布了一个问题并回家了。我收到的前几条回复并没有真正帮助,但第三条回复证明是关键。消息将我引向了 Petter Hesselberg 在 2001 年 11 月的 Windows 开发人员杂志上的文章 - "等待光标的正确时间和地点"。我找不到这篇文章,但源代码 在线,而关键是使用 AttachThreadInput
。一旦我将它添加到我的代码中,事情就开始工作了。
我实际上以为我已经完成了,直到我发现当我下拉菜单(或调出上下文菜单)时,光标变成了沙漏。问题在于,当菜单显示时,我的消息循环没有运行,因此计时器没有被重置。在经过一定程度的挠头之后,我想出了使用 GetMessage
钩子来重置计时器的想法,因为我猜想菜单一定还在调用 GetMessage
(或 PeekMessage
)。果然,这解决了菜单问题。(并且可能解决了一些相关问题,例如模式对话框。)
我又以为我已经完成了,但又发现了一个小故障。就在工具提示出现之前,光标闪烁成沙漏并返回。我猜工具提示在等待时不会调用 GetMessage
或 PeekMessage
。我通过简单地让我的计时器比工具提示计时器更长来修复了这个问题。
我的最后一个任务是将代码提取出来,我将其与我的消息循环混合在一起,变成更可重用的东西。我最终将其打包成一个 C++ 类。要使用该类,你只需在你的消息循环中创建一个它的实例。类似这样
#include "awcursor.h" ... while (GetMessage(&msg, NULL, 0, 0)) { AutoWaitCursor awc; TranslateMessage(&msg); DispatchMessage(&msg); }
AutoWaitCursor
构造函数“启动”计时器,而析构函数(在循环结束时自动调用)在必要时恢复光标。构造函数还负责第一次创建线程和钩子。
如果你查看代码,你可能会注意到我为多线程所做的唯一让步是声明了几个变量为 volatile。我没有尝试同步线程或阻止它们同时访问变量。这是一个有意的选择,因为我想使代码尽可能快,以免给消息循环增加开销。我如何证明这一点?首先,这些变量是普通的整数,因此读写它们都是原子性的。其次,如果万一发生同步问题,最坏的情况是光标可能短暂地错误。这是保持代码简单和快速所要付出的微小代价。实际上,我没有看到任何问题。
为简单起见,我将静态类成员的定义/初始化包含在头文件中。这不是最好的设置,因为它意味着你不能在多个源文件中包含此头文件。理想情况下,你会将定义放在一个单独的 .cpp 文件中。但是,你通常在你的程序中只有一个消息循环,所以这种设置似乎是可以接受的。
希望你发现代码和解释有用。祝你好运!