Win32 C++泛型函数,用于从UI线程或任意工作线程设置光标(SetCursor在外部线程中不起作用)
我需要在默认光标和等待光标之间切换。 我在应用中看到如果我这样做
// Start Waiting Cursor
SetCursor(LoadCursor(NULL, IDC_WAIT));
// ... App Logic ...
// Resume Normal Cursor when done
SetCursor(LoadCursor(NULL, IDC_ARROW));
这在直接的UI线程代码中能正确工作,但从分离的工作线程代码中却不起作用。为了在工作线程的某些条件下确定等待光标,我目前处理 WM_SETCURSOR,它检查全局变量。
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT /* Client area only*/ &&
threadCondition1 || threadCondition2 || threadCondition3) {
SetCursor(LoadCursor(NULL, IDC_WAIT));
} else {
// Default Arrow Cursor
return DefWindowProc(hWnd, message, wParam, lParam);
}
据Google说,这是因为
SetCursor在Win32 API中常常在单独线程中失败,因为光标状态(包括可见性和形状)是线程本地的,而不是全局的。要从工作线程更改光标,必须确保 [主UI线程] 处理WM_SETCURSOR消息,或者将线程输入附加以同步光标状态,如由 GitHub用户 描述。
- 解决方案1(推荐): 使用
PostMessage或SendMessage来通知UI线程改变光标形状,确保更改发生在正确的线程上。- 解决方案2(替代): 使用
AttachThreadInput将工作线程的输入状态与UI线程同步,从而让SetCursor能在两者之间工作。
然而,我的目标是拥有一个可以从任何地方调用、无需关心上下文的单一便捷函数。 我不喜欢在不同的地方分别实现。 理想情况下,一个通用的函数,例如
void SetAppCursor(boolean waiting) {
//... If waiting -> Set Waiting Cursor, else Normal Arrow
}
这是否意味着我应该始终在这个函数的主体中执行
PostMessage(hWndMain, WM_SETCURSOR, ...?, ...?)
,即使是UI线程逻辑,也要完全避免 SetCursor?这样做是否有任何缺点?对于这个通用调用,wParam/lParam 的取值应该是什么?
我找不到替代的 AttachThreadInput 的示例,看起来它的使用似乎被不鼓励。
解决方案
是的,你应该让 SetAppCursor() 向UI线程中的一个窗口发布/发送消息,然后让该窗口的处理程序在合适的时机调用 SetCursor()。
不过,我建议使用自定义消息来触发改变,而不是直接使用 WM_SETCURSOR。WM_SETCURSOR 是在鼠标移到你的窗口上时操作系统发送的通知消息。如果在工作线程想更新光标时鼠标并不在你的窗口上,你不应该立即改变光标,避免打断用户的工作。使用自定义消息可以让你的UI线程对变更请求做出反应,而不强制让改动不必要地可见。
例如:
AppCursor.h
void SetAppCursor(bool waiting);
main.c
...
#define APPWM_SETAPPCURSOR (WM_APP+1)
static UINT uiWaitCount = 0;
void SetAppCursor(bool waiting) {
PostMessage(hWndMain, APPWM_SETAPPCURSOR, (WPARAM)waiting, 0);
}
static HCURSOR GetWindowClassCursor(HWND hWnd) {
TCHAR szClassName[256];
GetClassName(hWnd, szClassName, 256);
WNDCLASS wc;
HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
GetClassInfo(hInstance, szClassName, &wc);
return wc.hCursor;
}
static HCURSOR DetermineCursorForWindow(HWND hWnd) {
if (uiWaitCount > 0)
return LoadCursor(NULL, IDC_WAIT);
HCURSOR hCursor = GetWindowClassCursor(hWnd);
if (hCursor != NULL)
return hCursor;
return LoadCursor(NULL, IDC_ARROW);
}
static void UpdateTheCursorForWindow(HWND hWnd) {
SetCursor(DetermineCursorForWindow(hWnd));
}
static bool IsMouseInWindow(HWND hWnd) {
POINT pt;
GetCursorPos(&pt);
HWND hWndAtPt = WindowFromPoint(pt);
while (hWndAtPt) {
if (hWndAtPt == hWnd) return true;
hWndAtPt = GetParent(hWndAtPt);
}
return false;
}
...
LRESULT WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case APPWM_SETAPPCURSOR:
if (wParam != 0)
++uiWaitCount;
else
--uiWaitCount;
if (IsMouseInWindow(hWnd))
UpdateTheCursorForWindow(hWnd);
return 0;
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT) {
UpdateTheCursorForWindow(hWnd);
return TRUE;
}
break;
...
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
SomeOtherWorkFile.c
#include "AppCursor.h"
// Start Waiting Cursor
SetAppCursor(true);
// ... App Logic ...
// Resume Normal Cursor when done
SetAppCursor(false);
如果你不想修改现有的窗口处理程序,那么你可以在UI线程中创建一个专用的 消息仅窗口 来让 SetAppCursor() 将消息发布/发送给它。