将鼠标光标更改为等待光标,然后启动工作线程并在线程完成时改回
Posted
技术标签:
【中文标题】将鼠标光标更改为等待光标,然后启动工作线程并在线程完成时改回【英文标题】:changing mouse cursor to wait cursor then starting worker thread and changing back on thread completion 【发布时间】:2019-09-16 19:50:59 【问题描述】:在较旧的 MFC 应用程序中,我必须向另一台计算机执行网络连接请求,如果计算机名称不正确,请求超时之前可能需要几秒钟。所以我正在启动一个工作线程来建立初始连接,以便用户界面仍然响应。
网络连接请求是由用户选择一个菜单项触发的,该菜单项会弹出一个对话框来填写目标计算机信息。当用户点击对话框上的 Ok 按钮时,使用工作线程处理网络连接请求。
我想要做的是将鼠标光标更改为等待指示器,然后在实际建立连接或尝试超时后移除等待指示器。
我遇到的是鼠标光标仍然是指针,并且鼠标光标没有变为等待指示器。
我最初的想法是我可以使用BeginWaitCursor()
函数更改鼠标光标。但是,这没有任何效果,我可以看到。
进一步阅读表明我还需要覆盖CScrollView
类的afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
方法,但是我似乎找不到任何有用的信息来描述我需要在该方法中做什么。 OnSetCursor()
方法似乎由于多种原因被调用,仅移动鼠标就会触发该方法中的断点。
看起来,在OnSetCursor()
方法中,我应该检测当前应用程序状态,并在此基础上使用SetCursor()
函数来设置以前使用LoadCursor()
加载的可能鼠标光标样式之一。见Prevent MFC application to change cursor back to default icon 和Change cursor for the duration of a thread
但是我不确定这是否是实际完成的方式以及OnSetCursor()
提供的参数的实际含义以及如何使用它们。
在上述两个 SO 帖子中的第二个中,似乎使用全局来决定是否调用默认的 CView::OnSetCursor()
方法。
【问题讨论】:
你试过CWaitCursor
吗?
@1201ProgramAlarm 我刚刚尝试过,我发现它确实将鼠标光标更改为等待指示。但是有一个本地范围要求,这意味着一旦菜单处理程序启动工作线程然后返回,CWaitCursor
对象就会超出范围。我发现如果我使用BeginWaitCursor()
,然后在启动工作线程后休眠,冻结UI,我也会看到鼠标光标等待指示。所以看来,除了BeginWaitCursor()
之外,我还需要做一些其他事情来将光标保持为等待光标。
您可以动态创建CWaitCursor
对象,但由于您的主消息线程一直在运行,因此无论如何可能会替换光标。当您尝试获取网络连接时,您如何处理用户选择另一个菜单选项(或其他交互)?您可能需要求助于“连接到服务器”应用程序模式对话框。
@1201ProgramAlarm 在建立连接并启动之前,大多数菜单项都被禁用并变灰。在连接存在之前,用户唯一能做的就是退出。什么是“连接到服务器”应用程序模式对话框?此应用程序使用 UDP “连接”到目标计算机,这实际上涉及通知目标服务器需要创建新的通信会话并提供使用新会话所需的令牌。所有通信都是通过 UDP 而不是 TCP。
您创建等待光标,创建一个对话框或窗口,其中包含某种应用程序模式的消息(保留应用程序其余部分的输入),可能还有一个“取消”按钮。一旦建立连接并且您的线程终止,请关闭对话框。
【参考方案1】:
首先声明以下全局变量:
BOOL bConnecting = FALSE; // TRUE if connecting, set by your application
HCURSOR hOldCursor = NULL; // Cursor backup
当你需要显示沙漏光标时调用:
bConnecting = TRUE;
hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
一旦连接建立(或失败)调用:
bConnecting = FALSE;
SetCursor(hOldCursor);
// Alternatively you can call SetCursor(LoadCursor(NULL, IDC_ARROW)); - no need to backup the cursor then
// Or even not restore the cursor at all, it will be reset on the first WM_MOUSEMOVE message (after bConnecting is set to FALSE)
你还需要覆盖OnSetCursor()
:
BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
if (bConnecting) return TRUE; // Prevent MFC changing the cursor
// else call the default
return CFrameWndEx::OnSetCursor(pWnd, nHitTest, message);
并将ON_WM_SETCURSOR()
指令添加到CMainFrame
的消息映射中,以启用OnSetCursor()
消息处理程序。
“主框架”是 MFC 应用程序中所有窗口的父级,这就是我们为它覆盖 OnSetCursor()
的原因。它会影响所有其他窗口。
在 MFC 环境中,您还可以使用 BeginWaitCursor()
、RestoreWaitCursor()
和 EndWaitCursor()
函数。这些是CCmdTarget
方法,可以使用AfxGetApp()
以及任何CWnd
派生类访问。
请注意,在具有 UI 线程和工作线程的多线程环境中使用全局变量,取决于线程如何使用和访问全局变量,您可能会产生竞争条件。
【讨论】:
设置等待光标(无论如何,如果通过 BeginWaitCursor() 或通过 CWaitCursor)还不够的原因是您在事后处理消息队列。当消息队列正在运行时,鼠标光标下方的窗口会收到一个 WM_SETCURSOR 事件以使该窗口有机会控制其上方的光标。如果窗口的线程(通常是主线程)很忙,设置一次等待光标将使该光标保持可见,直到重置它或处理 WM_SETCURSOR。 @Nick:没错,问题在于 MFC 确实处理了 WM_SETCURSOR 消息,所以我们需要提供一个覆盖。在许多代码示例中,我看到调用SetCursor()
和OnSetCursor()
(处理WM_SETCURSOR 消息),即使是鼠标移动也会被调用。相反,在我上面建议的解决方案中,我只设置了一次,然后简单地返回 TRUE(防止将其更改回来),直到它被重置。也就是说,OnSetCursor()
可能会被调用,但它不会做任何事情,并且返回 TRUE 将阻止系统将其发送到其他窗口。 ownign线程不一定要忙。
@Richard Chambers:感谢您编辑我的答案。关于您的消息映射注释,Visual Studio 中的属性/事件编辑器通常会为您执行此操作(添加声明和实现并修改消息映射),因此该过程非常自动化。这是大多数开发人员使用它的方式,这也是我没有特别提及的原因。至于多线程环境,确实,从不同线程访问/修改全局变量需要互锁或同步访问。
@ConstantineGeorgiou 您发布的答案帮助了我。当我与我的源代码一起实施您提出的解决方案时,我遇到了一些事情,这就是编辑的来源。我将它们添加到您的帖子中只是为了完整。这么多其他答案只是一半,我希望至少有一个关于这个主题和这个领域的相当完整的答案。【参考方案2】:
@Constantine 的 OnSetCursor 实现对我不起作用(VC++ 2013;Win 10) - 启动线程后等待光标仍返回箭头。但我用以下代码解决了我的问题。
BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
if (bConnecting)
SetCursor(LoadCursor(NULL, IDC_WAIT));
return TRUE; // Prevent MFC changing the cursor
// else call the default
return CFrameWndEx::OnSetCursor(pWnd, nHitTest, message);
请注意,如果您希望在将鼠标悬停在视图上时显示等待光标,则所有打开的对话框或视图都应具有 OnSetCursor。
【讨论】:
以上是关于将鼠标光标更改为等待光标,然后启动工作线程并在线程完成时改回的主要内容,如果未能解决你的问题,请参考以下文章
使用 Windows 窗体应用程序时如何将鼠标光标更改为自定义光标?
有没有办法使用python将Windows鼠标光标更改为自定义光标?