问题描述
这是我遇到的问题:
-
我需要将光标切换到(比如说)
IDC_WAIT
,直到我完成了一些操作。 -
SetCursor
只在鼠标移动前有效。 -
WM_SETCURSOR
仅在鼠标移动后有效。
你会认为我可以只做上述两个,同时调用 SetCursor
并修改 WM_SETCURSOR
的行为,以便我让光标改变直到某个点未来。
但我不能就那样做。为什么?因为 SetCursor
是应用程序范围的,但随机窗口不知道(也不应该知道)整个应用程序的正确光标是什么。它需要执行正确的命中测试才能将 WM_SETCURSOR
发送到实际位于光标下的窗口,目前尚不清楚正确的方法是什么。
在 Win32 中切换光标并返回的正确方法是什么?我看到的每个例子都是微不足道的,忽略了这些问题。
解决方法
1_ 与禁用输入结合使用时,实现繁忙游标是最简单的。首先禁用输入EnableWindow( hwnd,FALSE );
,然后设置忙光标SetCursor( LoadCursor( 0,IDC_WAIT ) );
。现在您可以进行一些操作(最好不要超过 5 秒)。之后启用输入EnableWindow( hwnd,TRUE );
。当您的操作时间超过 5 秒时,窗口会出现“重影”,因此会丢失标题栏上的繁忙光标和调整边框。
2_如果窗口在显示忙光标时需要接受输入,则不仅要在顶级窗口的窗口过程中处理 WM_SETCURSOR
消息,还要处理其所有子窗口(简单的 STATIC
控件是个例外)。这需要对这些子项进行子类化 (SetWindowSubclass
),除非您可以使用某些高级框架,否则这可能是一项任务。
在子类化窗口过程中,只需设置忙光标并返回TRUE
。对于 DefWindowProc
情况,请勿调用 DefSubclassProc
或 WM_SETCURSOR
。
switch ( message )
{
case WM_SETCURSOR:
SetCursor( MyCursor );
return TRUE;
...
}
子类化可以在每个子创建时完成,也可以在以后通过枚举它们来完成。
有趣的是,它甚至适用于带有弹出列表的菜单和组合框。
3_ 另一种选择是隐藏光标 ShowCursor( FALSE );
并显示具有一些点击功能的半透明窗口跟踪鼠标光标位置。就我个人而言,我会从只显示在当前光标位置上方或下方几个像素的窗口开始。
也许状态栏上的进度条或主窗口上的简单动画(沙漏?)会更容易。
,我想我找到了一个合理有效的解决方案。它并不完美(我仍然看到一些粗糙的边缘),但我认为到目前为止对我来说还可以,并且考虑到所有的权衡,它的行为比懒惰的方法更合理。
应用的复杂程度可能会有所不同。
以下是我建议对处于类似情况的人做的事情:
-
定义应用范围的消息,例如
WM_UPDATECURSOR = WM_APP + 0
。 -
通过触发
WM_UPDATECURSOR
使您的主 (GUI) 线程处理WM_SETCURSOR
:一个。使用
GetCursorPos
和WindowFromPoint
获取光标下的线程。B.使用
GetWindowThreadProcessId
检查进程和线程 ID。c.如果它处于不同的进程中,请停止。
d。如果它与您的进程在不同的线程中,请
PostThreadMessage(thread_id,WM_UPDATECURSOR)
到它,然后停止。 (这是极不寻常且通常很糟糕的做法;我只是为了完整性才提到它。)e.如果它是用于您自己的线程,则使用
WM_NCHITTEST
,如果成功,请找出(如下所述)正确 光标应该是什么(如果有)。如果有的话,用SetCursor
设置它;如果没有,SendMessage(WM_SETCURSOR)
。 -
保留与您认为“可堆叠”的不同游标相对应的计数器列表。较早的元素较低优先级。不在列表中的任何内容都不会在以后被覆盖或考虑。
-
每当您想更改任何线程上的光标时,如果需要,可以原子地增加或减少其相应的计数器。 (假设此游标在可堆叠游标列表中。)
然后以与上述WM_UPDATECURSOR
相同的方式触发游标更新。 -
要判断某个游标是否已在任何一点堆叠,请反向遍历列表,找到第一个正计数器对应的游标。如果没有,则返回
NULL
;没有堆叠光标。 -
在主窗口/对话框的
WM_SETCURSOR
处理程序中,按照上一步中的说明从全局列表中评估光标,并使用SetCursor
对其进行设置。但是,返回FALSE
以便子窗口(如编辑控件)在需要时仍会覆盖它。
如果我发现更多问题,我会更新它,但我认为总体上它表现得不错。