處理事件(Handle Event)

UI Toolkit Events類似於HTML Events,當事件(Event)發生時,該事件就會發送給target visual element以及在事件傳播路徑(propagation path)上的visual elements。

處理事件的順序如下:

  1. 由root element往下直到目標(target)的parent element,都會執行event callback,稱為trickle-down phase
  2. 在事件目標執行event callback,稱為target phase
  3. 在事件目標上面呼叫ExecuteDefaultActionAtTarget()
  4. 由事件目標的parent element往上直到root element都會執行event callbacks,稱為bubble-up phase
  5. 在事件目標上面呼叫ExecuteDefaultAction()

註冊Event Callbacks(Register an event callback)

可以在class中註冊callback,為個別的instance自訂行為(behavior),例如滑鼠點擊text label時作出反應。
除了目標(target)以外,沿著傳播路徑經過的element都會接收到兩次event

  1. 一次是在trickle-down phase
  2. 另外一次是bubble-up phase

使用element的RegisterCallback方法來註冊一個event callback

  • element.RegisterCallback<MouseDownEvent>(CallbackMethod); 此例中把CallbackMethod註冊到MouseDownEvent
1
myElement.RegisterCallback<MouseDownEvent>(MyCallback);

預設中,註冊的event callback會在target phasebubble-up phase的時候執行,這樣可以確保父element會在子element之後做出反應。

  • 如果你想要父element在子element之前做出反應可以在註冊event時,將其設定為TrickleDown.TrickleDown
1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;
using UnityEngine.UIElements;

...
VisualElement myElement = new VisualElement();

// 為這個myElement註冊一個trickle-down phase的callback,
// 這會讓dispatcher在target phase與trickle-down phase時執行此MyCallback
myElement.RegisterCallback<MouseDownEvent>(MyCallback, TrickleDown.TrickleDown);

// 定義一個callback
void MyCallback(MouseDownEvent evt) { /* ... */ }
...

在一個element中,你可以對同一個event註冊多個callback,但是你只能在同一事件和傳播階段註冊一次相同的callback。

有參數的callback
1
2
3
4
5
// 此將會向MyCallbackWithData傳送一個MyType的參數
myElement.RegisterCallback<MouseDownEvent, MyType>(MyCallbackWithData, myData);

// 定義一個有參數的callback
void MyCallbackWithData(MouseDownEvent evt, MyType data) { /* ... */ }

移除註冊的callback

使用myElement.UnregisterCallback()移除註冊的callback。


監聽value

UI controls會使用value屬性來存放資料,例如

  • Toggle會持有一個boolean屬性,當Toggle關閉(off)時設為false,開啟(on)時設為true。
  • IntegerField會持有一個integer屬性,用來保存該field的值。
    • int val = myIntegerField.value;
  • 透過RegisterValueChangedCallback你可以註冊一個監聽ChangeEvent事件的callback
    • RegisterValueChangedCallbackRegisterCallback<ChangeEvent>的簡寫,它是透過INotifyValueChange interface來為VisualElement推測正確的Type T
1
2
3
4
5
// 為myIntegerField註冊一個監聽值改變的OnIntegerFieldChange callback
myIntegerField.RegisterValueChangedCallback(OnIntegerFieldChange);

// 定義一個callback
void OnIntegerFieldChange(ChangeEvent<int> evt) { /* ... */ }

更改Value有以下方式

  • 直接更改value屬性,這會觸發一個新的ChangeEvent
    • myControl.value = myNewValue;
  • 使用該element的SetValueWithoutNotify()更改,這不會觸發一個新的ChangeEvent
    • myControl.SetValueWithoutNotify(myNewValue);

獲取Pointer

當visual element獲取一個Pointer時,Unity會向這個visual element發送所有與這個Pointer相關的Events,而不管是否這個Pointer懸停(hovers over)在這個visual element上,舉例來說,假設你建立了一個control,他會接收drag events並獲取pointer,不論pointer的位置為何,這個control都會接收drag events


Event與自訂Control

當自訂Control時,有兩種方式回應UI Toolkit events

  1. 註冊一個event callback
  2. 實作一個default action

callbacks與default action的差異

  • callback必須要註冊在一個實體物件上;default action則是類似在class上的virtual functions
  • 所有在傳播路徑(propagation path)上的visual element都會執行callback;Default actions只會在event target上執行。
  • callbacks可能會需要額外檢查是否是需要做出反應的event;default actions可以省略這個步驟
  • callbacks效能可能較差,default actions會好一些
    • 因為callbacks在propagation phase時需要在註冊表(callback registry)裡面搜尋,而default actions則不需要
實作Default Action

若要實作Default Action的話,需要繼承VisualElement,並實作ExecuteDefaultActionAtTarget()或是ExecuteDefaultAction(),或兩個方法都實作。
如果實作了Default Action,會應用到該class的所有實體(instances)

  • 如果你想要在parent callback之前執行default action可以把default action放到ExecuteDefaultActionAtTarget()
  • 應該將default actions視為該element type要有的行為(behaviors),例如:chcekbox觸發click event更新它的狀態時應override default action,而不是為所有checkbox註冊一個callback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override void ExecuteDefaultActionAtTarget(EventBase evt)
{
// Call the base function.
base.ExecuteDefaultActionAtTarget(evt);

if (evt.eventTypeId == MouseDownEvent.TypeId())
{
// ...
}
else if (evt.eventTypeId == MouseUpEvent.TypeId())
{
// ...
}
// More event types
}

custom controls的最佳實踐

  1. 如果是element有的行為(behaviors),你應該實作在default action
  2. 在callback中,你可以使用PreventDefault()來取消該element的 default action
  3. 若一個instance沒有callback,那麼它不會進入propagation process
  4. 在事件分派過程(event dispatch process)中,有兩個時段可以執行你的default action
    • 若想在trickle-downbubble-up之間,在target callbacks之後立刻執行的話,override這個ExecuteDefaultActionsAtTarget()方法
    • 在事件分派過程(event dispatch process)結束時,override這個ExecuteDefaultActions()方法
  5. 如果可能的話,盡量將default action寫在ExecuteDefaultActions(),在事件傳播過程(event propagation process)中的trickle-down階段或是bubble-up階段,可以透過PreventDefault()來覆蓋預設的操作。
  6. 如果一個事件不應該傳播到parent element的話,你必須要把傳播停下
    • 例如:一個TextField在收到一個KeyDownEvent去修改它的值時,不應該讓這個事件傳播到它的parent visual element
    • 使用ExecuteDefaultActionsAtTarget()實作default action時,可以呼叫StopPropagation()這可以確保event在bubble-up階段時不會處理
  7. Default actions僅針對event target執行,要讓class對target的child element或parent element的事件作出反應,則必須在event的trickle-down或是bubble-up傳播階段之一註冊callback以接收該事件。
  8. 為了提高性能,盡量避免在的class中註冊回調函式。
  9. 一個parent panel可以在trickle-down階段停止傳播,避免事件傳到它的children
  10. 你無法在event class本身中阻止EventBase.PreDispatch()EventBase.PostDispatch()方法的執行
  11. 可以透過以下方法事件傳播(event propagation)
    • StopImmediatePropagation():立刻停止事件傳播,其他callback不會執行這個event,但是ExecuteDefaultActionAtTarget()ExecuteDefaultAction() default actions 仍然會執行。
    • StopPropagation():在這個element上的最後一個callback執行完之後,停止傳播。這可以確保所有在這個element上的callback都執行完,不會有更多element去對這個事件做出反應。ExecuteDefaultActionAtTarget()ExecuteDefaultAction() default actions 仍然會執行。
    • PreventDefault():在事件傳播過程(event propagation process)時,避免呼叫ExecuteDefaultActionAtTarget()ExecuteDefaultAction()
      • PreventDefault()不會阻止其他callback的執行。
      • 在bubble-up階段時,PreventDefault()不會影響ExecuteDefaultActionAtTarget() action。

Reference:https://docs.unity3d.com/Manual/UIE-Events-Handling.html

評論