QueryCursor 是 WPF 用来"按需决定当前鼠标光标显示什么"的核心机制之一。它不是"鼠标移入/移出"那类通知事件,而是一个光标查询流程 :当 WPF 需要显示/更新光标时,会沿着可视树触发 Mouse.QueryCursorEvent,让控件/元素有机会在这一刻设置 e.Cursor 和 e.Handled。
1) QueryCursor 事件的功能(解决什么问题)
1.1 动态设置光标
当鼠标位于一个控件上时,光标应该是什么(箭头、手型、十字、拖拽、缩放......)往往取决于当前状态:
- 是否处于"选择/拖拽/绘制/缩放"模式
- 鼠标是否压在某个"可交互热点"(边框、控制点、命中区域)上
- 是否允许操作(禁用、只读、锁定)
QueryCursor 的作用就是:在光标显示之前的最后一步,根据当前上下文决定光标。
1.2 优先级与路由控制
QueryCursor 是一个 路由事件(RoutedEvent) (通常以隧道 PreviewQueryCursor + 冒泡 QueryCursor 形式出现)。典型流程是:
- 先触发
PreviewQueryCursor(从根到目标) - 再触发
QueryCursor(从目标向根)
任何一层如果设置了:
e.Cursor = ...e.Handled = true
就可以"截断/终止"后续处理,保证自己的光标决定权。
1.3 与 Cursor 属性的关系
- 你可以直接设置
UIElement.Cursor = Cursors.Hand固定光标。 - 也可以用
QueryCursor在运行时不断动态决定光标(更适合复杂交互)。
什么时候会触发 QueryCursor
常见触发时机包括:
- 鼠标在元素上移动、进入/离开元素区域
- 捕获(Capture)变化导致命中目标变化
- 某些情况下显式调用
Mouse.UpdateCursor() - 你代码里手动 Raise
Mouse.QueryCursorEvent(就像你文件里的UpdateCursor())
这说明你的控件把光标策略委托给 IState / IViewState(状态机):
- 不同的 State 决定不同光标(选择、拖动、绘制、调整大小)
- 光标的决策集中在状态对象中,更容易维护,也与鼠标行为(MouseDown/Move/Up)保持一致
这是 QueryCursor 的经典用法:鼠标交互采用"状态驱动",光标作为状态的外在反馈。
4) 典型应用场景
4.1 画布/编辑器类控件(最常见)
- CAD、流程图、图像标注、ROI 框、形状编辑
- 鼠标悬停到控制点显示
SizeNWSE/SizeWE - 悬停到形状主体显示
SizeAll或Hand - 处于绘制模式显示
Cross
4.2 拖拽/缩放/平移
- 按下空格进入平移模式,光标变成抓手
- 滚轮缩放时提示缩放光标
- 鼠标捕获期间保持同一光标(便于 UX 一致)
4.3 权限/可用性提示
- 只读区域显示
No(禁用符号) - 不允许拖拽时保持箭头或显示禁止
4.4 覆盖默认控件行为
有些控件(比如 TextBox)对光标有默认处理。你可以通过 PreviewQueryCursor 抢先处理,或者在更高层统一光标策略。
5) 使用 QueryCursor 的关键点(避免踩坑)
-
一定要设置
e.Handled = true才能保证生效否则后续元素可能覆盖你的
e.Cursor。 -
尽量不要做重计算
QueryCursor可能触发频繁;命中测试/状态计算建议缓存或用轻量逻辑。 -
状态变化但鼠标没动时要主动更新
- 调用
Mouse.UpdateCursor()或你现在的UpdateCursor()触发一次查询。
- 调用
-
在控件内部统一入口很合理
你现在的
GetState()?.QueryCursor(...)就是把复杂逻辑隔离到状态层,属于良好架构。
了解更多
ContentElement.QueryCursor Event
Mouse.QueryCursor Attached Event
System.Windows.Controls 命名空间 | Microsoft Learn
控件库 - WPF .NET Framework | Microsoft Learn
使用 Visual Studio 创建新应用教程 - WPF .NET | Microsoft Learn
HeBianGu的个人空间-HeBianGu个人主页-哔哩哔哩视频