Version: 2020.2
事件系统
事件响应

分发事件

The UI Toolkit event system listens to events, coming from the operating system or scripts, and dispatches these events to visual elements using the EventDispatcher. The event dispatcher determines an appropriate dispatching strategy for each event it sends. Once determined, the dispatcher executes the strategy.

视觉元素和其他支持类实现了几个事件的默认行为。有时,涉及创建和发送额外的事件。例如,MouseMoveEvent 可能生成额外的 MouseEnterEventMouseLeaveEvent。这些额外的事件将放置在队列中,并会在当前事件完成后处理这些事件。例如,完成 MouseMoveEvent 后处理 MouseEnterEventMouseLeaveEvent 事件。

事件目标

EventDispatcher.DispatchEvent() 的首个任务是查找事件目标。

有时,这很容易,因为已经设置事件 target 属性。但是,对于源自操作系统的大多数事件,则并非如此。

事件的目标取决于事件类型。对于鼠标事件,目标通常是直接位于鼠标下面最上层的可选择元素。对于键盘事件,目标是当前聚焦的元素。

找到目标后,目标会存储在 EventBase.target 中,且在分发过程中不会改变。属性 Event.currentTarget 会更新为当前正在处理该事件的元素。

拾取模式和自定义形状

大多数鼠标事件使用拾取模式来确定其目标。VisualElement 类有一个支持以下值的 pickingMode 属性:

  • PickingMode.Position(默认值):根据位置矩形执行拾取。
  • PickingMode.Ignore:阻止因鼠标事件而拾取。

可以覆盖 VisualElement.ContainsPoint() 方法来执行自定义的交集逻辑。

捕获鼠标

有时,在鼠标按下后,元素必须捕获鼠标位置以确保所有后续的鼠标事件都只发送给自己(即使指针不再悬停在该元素上)。如果一个控件会响应鼠标按下和鼠标松开序列 - 在鼠标按下和鼠标松开事件之间可能会移动鼠标,通常便会有此要求。例如,在单击按钮、滑动条或滚动条时就属于这种情况。

要捕获鼠标,请调用 element.CaptureMouse()MouseCaptureController.CaptureMouse()

要释放鼠标,请调用 MouseCaptureController.ReleaseMouse()。如果在调用 CaptureMouse() 时已经有另一个元素正在捕获鼠标,该元素将收到 MouseCaptureOutEvent 并失去此捕获。

在应用程序中,任何时候都只能有一个元素具有捕获。当元素具有捕获时,该元素便是除鼠标滚轮事件之外的所有后续鼠标事件的目标。

注意:这只适用于尚未设置目标的鼠标事件。

焦点环和 Tab 顺序

每个 UIElement 面板都有一个焦点环用于定义元素的焦点顺序。默认情况下,元素的焦点顺序是通过对视觉元素树执行深度优先搜索 (DFS) 来确定的。例如,以下所示的树的焦点顺序将是 F、B、A、D、C、E、G、I、H。

焦点顺序
焦点顺序

有些事件使用焦点顺序来确定哪个元素获得焦点。例如,键盘事件的目标是当前获得焦点的元素。

使用 focusable 属性可控制元素的可聚焦性。默认情况下,无法聚焦 VisualElements,但是默认可聚焦某些子类(例如 TextField)。

使用 tabIndex 属性可按如下方式控制焦点顺序(tabIndex 默认值为 0):

  • 如果 tabIndex 为负,则无法用 Tab 键导航该元素。
  • 如果 tabIndex 为零,该元素保持默认的 Tab 顺序(通过焦点环算法确定的顺序)。
  • 如果 tabIndex 为正,该元素将置于 tabIndex 为零 (tabIndex = 0) 或具有更高 tabIndex 的其他元素之前。

事件传播

选择事件目标后,分发程序将计算事件的传播路径。传播路径是接收事件的视觉元素的有序列表。

元素列表的获得方式是从视觉元素树的根开始,朝着目标往下,再朝着树的根往上。

传播路径
传播路径

传播路径的第一阶段从根下降到目标父级。此阶段称为涓滴 (trickle down) 阶段

传播路径的最后一个阶段从目标父级上升到根。此阶段称为冒泡 (bubble-up) 阶段

事件目标位于传播路径中间。

大多数事件类型将发送到传播路径上的所有元素。但是,一些事件类型会跳过冒泡阶段。此外,一些事件类型仅发送到事件目标。

如果元素被隐藏或禁用,它将不会接收事件。事件仍会传播到被隐藏或禁用的元素的祖先和后代。

在沿着传播路径发送事件时,Event.currentTarget 更新为当前正在处理事件的元素。这意味着,在事件回调函数中,Event.currentTarget 是对回调注册的元素,而 Event.target 是发生事件的元素。

事件类型的分发行为

每个事件类型都有自己的分发行为。下表以三列的形式汇总了每个事件类型的行为:

  • 涓滴:在涓滴阶段将此类型的事件发送到元素。
  • 冒泡:在冒泡阶段将此类型的事件发送到元素。
  • 可取消:此类事件可以取消、停止或阻止默认操作的执行。
涓滴 冒泡 可取消
MouseCaptureOutEvent
MouseCaptureEvent
ChangeEvent
ValidateCommandEvent
ExecuteCommandEvent
DragExitedEvent
DragUpdatedEvent
DragPerformEvent
DragEnterEvent
DragLeaveEvent
FocusOutEvent
BlurEvent
FocusInEvent
FocusEvent
InputEvent
KeyDownEvent
KeyUpEvent
GeometryChangedEvent
MouseDownEvent
MouseUpEvent
MouseMoveEvent
ContextClickEvent
WheelEvent
MouseEnterEvent
MouseLeaveEvent
MouseEnterWindowEvent
MouseLeaveWindowEvent
MouseOverEvent
MouseOutEvent
ContextualMenuPopulateEvent
AttachToPanelEvent
DetachFromPanelEvent
TooltipEvent
IMGUIEvent

  • 2018–11–02 页面已修订
事件系统
事件响应