【问题标题】:What's the correct way to tentatively switch to a wait cursor in Win32? (Or: How to trigger WM_SETCURSOR properly?)在Win32中试探性切换到等待光标的正确方法是什么? (或者:如何正确触发 WM_SETCURSOR?)
【发布时间】:2021-05-26 05:50:15
【问题描述】:

这是我遇到的问题:

  • 我需要将光标切换到(比如说)IDC_WAIT,直到我完成某些操作。

  • SetCursor只在鼠标移动前有效。

  • WM_SETCURSOR只有在鼠标移动后才有效。

您会认为我可以同时进行上述两种操作,同时调用SetCursor 并修改WM_SETCURSOR 的行为,这样我就可以让光标在未来某个时间点发生变化。

但我不能这样做。为什么?因为SetCursor 是应用程序范围的,但随机窗口不知道(也不应该)正确的光标是整个应用程序。它需要执行正确的命中测试以将WM_SETCURSOR 发送到实际位于光标下的窗口,目前还不清楚正确的方法是什么。

在 Win32 中切换和返回光标的正确方法是什么?我看到的每一个例子都是微不足道的,忽略了这些问题。

【问题讨论】:

  • 应用程序范围是您想要的。使用GetCursor(),SetCursor(),挂起消息循环的慢东西,SetCursor()来恢复。如果缓慢的东西实际上没有挂起消息循环(不清楚),那么它会变得更加困难,因为所有子窗口都必须合作以保持沙漏。通常只在框架中实用,与 Winform 的 Control.UseWaitCursor 相比。
  • @HansPassant:即使我在应用程序范围内执行此操作,当我(比如说)调整边框大小或悬停在编辑框上时,光标仍然需要保持不受影响,否则会令人困惑。并且在操作仍在进行时,1像素的移动会突然引起变化根本没有意义,所以即使我愿意立即致电SetCursor更改它,我仍然有这个问题应用程序-宽的。这里没有任何东西会挂起消息循环——这是主线程正在启​​动/停止的另一个线程上的后台操作——但是人们是如何做到这一点的呢?
  • 他们没有,弹出“正在处理它”对话框是标准方法。
  • @HansPassant:有趣的是,你提到了这一点,这正是我想要摆脱的,因为它对用户不友好并且没有必要阻止 UI 或弹出一个额外的窗口来显示我的身份正在做。所以真的没有好的解决办法吗? :(如果是这样,请随时将其发布为答案...
  • 通过明显地禁用在工作线程被占用时无法安全使用的控件变得更加友好。就像启动它的按钮一样。

标签: c winapi mouse-cursor


【解决方案1】:

我想我找到了一种可行的解决方案。它并不完美(我仍然看到一些粗糙的边缘),但我认为到目前为止对我来说还可以,并且考虑到所有权衡,它的行为比惰性方法更合理。

您的里程可能会因应用的复杂程度而异。

以下是我建议对处于类似情况的人做的事情:

  1. 定义应用范围的消息,例如WM_UPDATECURSOR = WM_APP + 0.

  2. 通过触发WM_SETCURSOR 让您的主(GUI)线程进程WM_UPDATECURSOR

    一个。使用GetCursorPosWindowFromPoint获取光标下的线程。

    b.使用GetWindowThreadProcessId检查进程和线程ID。

    c。如果它处于不同的进程中,请停止。

    d。如果它与您的进程在不同的线程中,PostThreadMessage(thread_id, WM_UPDATECURSOR) 到它,然后停止。 (这是非常不寻常且通常很糟糕的做法;我只是为了完整性而提及它。)

    e。如果它用于您自己的线程,则使用WM_NCHITTEST,如果成功,则找出(如下文进一步解释)正确 光标应该是什么,如果有的话。如果有的话,用SetCursor 设置它;如果没有,SendMessage(WM_SETCURSOR)

  3. 保留与您认为“可堆叠”的不同游标相对应的计数器列表。较早的元素的优先级较低。任何不在列表中的内容都不会被覆盖或稍后考虑。

  4. 当你想改变任何线程上的光标时,增加或减少其对应的计数器,如果需要的话,原子的。 (假设此游标在可堆叠游标列表中。)
    然后以与上述WM_UPDATECURSOR 相同的方式触发游标更新。

  5. 要确定光标是否在任何点堆叠,请反向遍历列表,找到与第一个正计数器对应的光标。如果没有,则返回NULL;没有堆叠光标。

  6. 在您的主窗口/对话框的WM_SETCURSOR 处理程序中,按照上一步中的说明从全局列表中评估光标,然后使用SetCursor 设置它。但是,返回FALSE,这样子窗口(如编辑控件)仍然可以根据需要覆盖它。

如果我发现更多问题,我会更新这个,但我认为总体上它表现得不错。

【讨论】:

  • 如前所述,如果鼠标被捕获,Windows 将停止发送WM_SETCURSOR 消息。这样做的直接含义是,如果鼠标在仍被捕获的情况下移出捕获窗口,则鼠标光标的行为仍与在窗口内捕获时一样。在这种情况下,从鼠标位置下方的窗口中查询光标会导致不同的行为。
  • @dialer:正如答案中提到的,它并不完美,仍然有粗糙的边缘......
【解决方案2】:

1_ 与禁用输入结合使用时,最容易实现繁忙光标。首先禁用输入EnableWindow( hwnd, FALSE );,然后设置忙碌光标SetCursor( LoadCursor( 0, IDC_WAIT ) );。现在您可以进行一些操作(最好不要超过 5 秒)。之后启用输入EnableWindow( hwnd, TRUE );。当您的操作时间超过 5 秒时,窗口将“重影”,因此它会丢失标题栏上的繁忙光标和调整边框。

2_如果窗口在显示忙碌光标时需要接受输入,你不仅要在顶层窗口的窗口过程中处理WM_SETCURSOR消息,还要对其所有子窗口处理(简单的STATIC控件除外) .这需要对这些孩子进行子类化 (SetWindowSubclass),除非您可以使用一些高级框架,否则这可能是一项艰巨的任务。

在子类化窗口过程中,只需设置忙碌光标并返回TRUE。不要打电话给DefWindowProcDefSubclassProc 处理WM_SETCURSOR 的情况。

switch ( message )
{
case WM_SETCURSOR:
    SetCursor( MyCursor );
    return TRUE;
...
}

子类化可以在每个子创建时或以后通过枚举它们来完成。

有趣的是,它甚至适用于带有弹出列表的菜单和组合框。

3_ 另一种选择是隐藏光标ShowCursor( FALSE ); 并显示半透明窗口跟踪鼠标光标位置,并具有一些点击功能。就我个人而言,我会从在当前光标位置上方或下方仅显示几个像素的窗口开始。

也许状态栏上的进度条或主窗口上的简单动画(沙漏?)会更容易。

【讨论】:

  • 不幸的是,我没有也不能,整个目的是让应用程序在我进行操作时仍然可用。不过感谢您的尝试。
  • 不能用,WM_SETCURSOR是发送的,不是贴出来的。所以只能在窗口过程中被拦截。
  • @HansPassant 你是对的。我检查了我的旧源代码和自定义框架自动将所有控件子类化,因此if ( msg == WM_SETCURSOR ) 在一个过程中,而不是消息循环中。很遗憾,这个答案是错误的。
  • @user541686 我已经改变了我的答案,所以它可能更有用。我已经验证了选项 1、2 和 3。至少在 Windows 10 上它们可以工作。
  • @DanielSęk:我不知道我是否需要走子类化路线,但这似乎是一种可能。感谢更新。 +1
猜你喜欢
  • 2014-05-08
  • 2013-01-09
  • 1970-01-01
  • 2013-10-15
  • 1970-01-01
  • 2017-09-13
  • 1970-01-01
  • 2013-03-24
  • 1970-01-01
相关资源
最近更新 更多