响应事件
您可以使视觉元素使用以下两种方式来响应它收到的事件:
- 通过注册事件回调(event callback),
- 通过实施默认操作
是否应注册事件回调或实现默认操作取决于许多因素。
例如,如果您从现有类实例化对象,并且希望实例在接收事件时以特定方式做出反应,那么您唯一的选择是在此实例上注册回调。
但是,如果从VisualElement派生一个新类,并且希望此类的所有实例对事件做出相同的反应,则可以在构造函数中对此类的所有实例注册回调,或者为该类实现默认操作。
回调和默认操作之间的根本区别如下:
- 回调必须在类的实例上注册,而默认操作在类上实现为虚函数。
- 回调将会对传播路径上的所有元素执行,而默认操作仅对事件目标执行。
您可以通过调用event.PreventDefault()
来阻止执行默认操作。某些事件类型是无法取消的,这意味着无法取消其默认操作。您可以通过调用event.StopPropagation()
或event.StopImmediatePropagation()
来阻止回调执行,甚至对于不可取消的事件也可有效阻止。
您应该将默认操作视为指定类型的元素在收到事件时应具有的行为。
例如,复选框需要通过响应Click事件来切换其选择状态。此行为应通过覆盖默认操作虚函数来实现,而不是在所有复选框上注册回调。
通常,您应该优先使用默认操作实现元素中自然需要的行为。这将保证元素的默认行为在实例上可以被取消(通过在实例附加的回调中,调用实例基类的PreventDefault()方法)。
将行为实现为默认操作的其它好处是,它们的执行不需要在回调注册表中进行查找,并且没有回调的实例会在涓滴/冒泡的传播过程中得到优化。
为了获得更大的灵活性,事件目标的默认操作可以在事件调度过程中的两个时刻执行:
- 通过覆盖
ExecuteDefaultActionsAtTarget(),
该函数执行发生在涓滴阶段和冒泡上升传播阶段之间,在事件目标的回调执行完毕之后; - 通过覆盖
ExecuteDefaultActions(),
该函数执行发生在整个事件发送过程结束时。
尽可能在ExecuteDefaultActions()
中实现本类的默认操作。这为您的class用户提供了更多选项来覆盖它们:他们可以在事件传播过程的涓滴阶段或冒泡阶段进行PreventDefault()
调用。
但是,有时您必须强制使得对类的执行默认操作的事件不会传播到元素的父级。例如,文本字段接收修改其值的按键事件。这些按键事件不会传播到父级,例如,使用Delete键删除内容。在这种情况下,使用ExecuteDefaultActionsAtTarget()
和调用实现默认操作StopPropagation()
,这样可确保在冒泡阶段事件不会被处理。
默认操作仅对事件的目标执行。当元素是由于事件的传播过程而接收到事件时,不会执行默认操作。如果您希望您的class对针对其子节点或其父节点的事件作出反应,您必须在class的每个实例上注册回调。这有时是必要的,但为了更好的可扩展性和更好的性能,请尽量避免在类的每个实例上注册回调。
注册事件回调
要将自定义行为添加到可视元素的特定实例,必须为触发自定义行为的事件注册回调。
注册事件回调的优点是,它允许您自定义现有类的单个实例的行为。注册事件回调的缺点是性能较差,因为执行时间较长。执行需要更长时间的原因是,每当收到事件时,都会检查已注册的事件以确定应该执行哪个回调。
例如,以下代码为MouseDownEvent
注册了两个回调:
// Register a callback on a mouse down event
myElement.RegisterCallback<MouseDownEvent>(MyCallback);
// Same, but also send some user data to the callback
myElement.RegisterCallback<MouseDownEvent, MyType>(MyCallbackWithData, myData);
您的回调函数声明应如下所示:
void MyCallback(MouseDownEvent evt) { /* ... */ }
void MyCallbackWithData(MouseDownEvent evt, MyType data) { /* ... */ }
您可以根据需要为活动注册尽可能多的回调。但是,回调注册系统会阻止您在给定事件和传播阶段多次注册相同的回调函数。您可以VisualElement
通过调用myElement.UnregisterCallback()
方法从a中删除回调。
除了目标之外,沿着传播路径的每个元素接收事件两次:一次在滴流阶段期间,一次在泡沫化阶段期间。要避免两次执行已注册的回调,请使用optional RegisterCallback
指定在哪个阶段执行回调。
默认情况下,在目标阶段和冒泡阶段执行已注册的回调。此默认行为可确保父元素在其子元素之后作出反应。例如,如果您希望父级首先做出反应,要覆盖其子级的行为,则应使用以下TrickleDown.TrickleDown
选项注册回调:
// Register a callback for the trickle down phase
myElement.RegisterCallback<MouseDownEvent>(MyCallback, TrickleDown.TrickleDown);
这通知调度程序在目标阶段和涓滴阶段执行回调。
实施默认操作
默认操作适用于该类的所有实例。这意味着为元素接收的每个事件调用一个或两个方法。
实现默认操作的类也可以在其实例上注册回调。
事件类实现在处理事件之前或之后执行的行为。events类通过重写类PreDispatch()
的PostDispatch()
方法来完成此操作EventBase
。由于事件是排队的,因此这些方法可以在处理事件时更新状态或执行任务,而不是在发出事件时。例如,在BlurEvent
执行元素之前更改当前聚焦的元素。
实现默认操作需要派生一个新的子类VisualElement
并实现ExecuteDefaultActionAtTarget()
方法,ExecuteDefaultAction()
方法或两者。
默认操作是在接收事件时由可视元素子类的每个实例执行的操作。您可以通过重写自定义默认行为ExecuteDefaultActionAtTarget()
和ExecuteDefaultAction()
:
override void ExecuteDefaultActionAtTarget(EventBase evt)
{
// Do not forget to call the base function.
base.ExecuteDefaultActionAtTarget(evt);
if (evt.GetEventTypeId() == MouseDownEvent.TypeId())
{
// ...
}
else if (evt.GetEventTypeId() == MouseUpEvent.TypeId())
{
// ...
}
// More event types
}
为了获得更大的灵活性,您应该在中实现默认操作ExecuteDefaultAction()
。这允许用户在需要时停止或阻止执行默认操作。
如果希望在其父回调之前执行目标默认操作,请在其中实现默认操作ExecuteDefaultActionAtTarget()
。
事件处理顺序
当事件发生时,它将沿着可视元素树中的传播路径发送。假设事件类型要求遵循事件传播的所有阶段,则将事件发送到每个可视元素。事件处理顺序如下:
- 对从根元素到事件目标的父元素的元素执行事件回调。这是调度过程的涓滴阶段。
- 在事件目标上执行事件回调。这是调度过程的目标阶段。
- 召唤目标
ExecuteDefaultActionAtTarget()
。 - 对从事件目标父级到根的元素执行事件回调。这是调度过程的泡沫化阶段。
- 召唤目标
ExecuteDefaultAction()
。
当事件沿传播路径发送时,Event.currentTarget
将更新为当前处理事件的元素。这意味着在事件回调函数中,Event.currentTarget
是回调被注册Event.target
的元素,并且是发生事件的元素。
停止事件传播并取消默认操作
您可以使用回调来停止,阻止和取消执行操作。但是,您无法阻止EventBase.PreDispatch()
和EventBase.PostDispatch()
方法的执行。
以下方法会影响事件传播和默认操作:
StopImmediatePropagation()
立即停止事件传播过程。没有为此事件执行其他回调。然而,ExecuteDefaultActionAtTarget()
和ExecuteDefaultAction()
默认操作仍在执行。StopPropagation()
在当前元素的最后一个回调之后停止事件传播过程。这确保执行当前元素上的所有回调,但没有其他元素对事件做出反应。在ExecuteDefaultActionAtTarget()
和ExecuteDefaultAction()
默认操作仍在执行。PreventDefaultAction()
阻止调用ExecuteDefaultActionAtTarget()
和ExecuteDefaultAction()
默认操作。PreventDefaultAction()
不会阻止执行其他回调。此外,如果您PreventDefaultAction()
在冒泡阶段进行调用,ExecuteDefaultActionAtTarget()
则不会阻止默认操作,因为它已被执行。该ExecuteDefaultActionAtTarget()
默认操作期间涓滴阶段执行。