C++ WinAPI 鼠标移出窗口后自定义系统光标仅生效一次问题排查
代码存在的核心错误
- 光标句柄提前销毁:
SetSystemCursor函数执行后会接管传入的光标句柄,此时你主动调用DestroyCursor销毁句柄,会导致第一次使用后my_cur和def_arrow_cur都变为无效句柄,第二次调用SetSystemCursor时传入无效句柄自然无法生效,这是效果仅触发一次的直接原因。 - API使用场景错误:
SetSystemCursor是全局修改系统默认光标的API,会影响所有运行中的程序,完全不适合实现单个应用的光标切换需求,还会导致程序退出后系统光标无法自动恢复的问题。 - 鼠标事件监听配置错误:你在
WM_CREATE和WM_MOUSELEAVE中注册的是TME_HOVER悬停事件,和你需要监听的鼠标离开逻辑不匹配,多余的事件注册会带来不必要的性能开销。
正确实现方案
你的需求不需要修改全局系统光标,只需要处理 WM_SETCURSOR 消息,结合鼠标进出状态来设置光标即可:
- 提前加载好两个光标,程序运行期间不要销毁,退出时再统一销毁
- 监听
WM_MOUSEMOVE时注册TME_LEAVE事件,监听鼠标离开 - 处理
WM_SETCURSOR消息,根据bMouseInside状态返回对应的光标 - 程序退出时销毁所有加载的光标资源
修正后核心代码示例
// 全局光标变量,运行期间不要销毁 HCURSOR def_arrow_cur = LoadCursor(NULL, IDC_ARROW); HCURSOR my_cur = LoadCursorFromFile(TEXT("my.cur")); LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { TRACKMOUSEEVENT tme; static BOOL bMouseInside = FALSE; switch (message) { case WM_CREATE: { RECT rt; POINT pt; GetClientRect(hWnd, &rt); ClientToScreen(hWnd, (LPPOINT)&rt); ClientToScreen(hWnd, (LPPOINT)&rt+1); GetCursorPos(&pt); bMouseInside = PtInRect(&rt, pt); } break; case WM_MOUSEMOVE: if (!bMouseInside) { bMouseInside = TRUE; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hWnd; tme.dwHoverTime = HOVER_DEFAULT; _TrackMouseEvent(&tme); } break; case WM_MOUSELEAVE: bMouseInside = FALSE; break; case WM_SETCURSOR: // 仅客户区走自定义光标逻辑 if (LOWORD(lParam) == HTCLIENT) { SetCursor(bMouseInside ? def_arrow_cur : my_cur); return TRUE; } return DefWindowProc(hWnd, message, wParam, lParam); case WM_DESTROY: // 退出前销毁加载的自定义光标 DestroyCursor(my_cur); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
如果你确实需要全局修改系统光标(不推荐),需要注意不要销毁传入SetSystemCursor的句柄,并且在程序退出时调用SystemParametersInfo(SPI_SETCURSORS, 0, NULL, 0)恢复系统所有默认光标。
内容的提问来源于stack exchange,提问作者Miriel
相关产品推荐
相关产品推荐

