本文来自Unity官方文档,https://docs.unity3d.com/Manual/UIE-Events.html ,具体内容可自行查看。
事件系统
UIElements包括一个事件系统,能够让用户和可视元素进行通信交互。收到HTML事件的启发,UIElements事件系统有很多与HTML事件相同的术语。UIElement事件系统包括以下内容:
1.调度系统 2.响应事件 3.合成事件 4.事件类型
根据本人的使用,感觉上这个事件系统还不是特别好用(至少对我来说),我有很多地方都是一知半解,并且官方文档在很多方面都有些模糊,因此这里只会简单介绍。
调度系统
UIElements通过监听操作系统或者脚本来将事件分发给VisualElement。事件调度器使用适当的调度策略来发送事件,一旦确信可以调度,就会执行该策略。VisualElement为多个事件实现了默认行为,有时这也包括了额外事件,这些额外的事件会放在队列中,在当前事件完成后再处理。
事件目标
略
拾取模式和自定义形状(Picking mode and custom shapes)
VisualElement类有一个pickingMode支持下列值属性:
- PickingMode.Position (默认值):根据矩形形状执行拾取
- PickingMode.Ignore:不被拾取
- 可以重写VisualElement.ContainsPoint()方法来自定义拾取逻辑
捕获鼠标
略
在任一时间在程序内只有一个元素能被拾取。当一个元素已经拾取到了鼠标,那么它是除了鼠标滚轮时间外所有后续鼠标事件的目标。
注意:这仅适用于尚未设置其目标的鼠标事件。
聚焦环和Tab指令(Focus ring and the tab order)
每个UIElement面板都有一个聚焦环(Focus ring),用于定义元素的聚焦顺序。默认情况下,通过在可视元素树上执行深度优先搜索(DFS)来确定元素的焦点顺序。例如,下面描述的树的焦点顺序是F,B,A,D,C,E,G,I,H。(利用这个可以实现Tab键切换元素)
某些事件使用焦点顺序来确定哪个元素持有焦点,例如,键盘事件的目标是当前的焦点元素。
使用元素的focusable属性可以控制元素的可聚焦性,默认情况下,VisualElements不可聚焦,但默认情况下某些子类(例如TextField,可能是可聚焦的)。
事件传播(Event propagation)
元素列表通过从可视元素树的根开始,向目标下降,然后向树上升到树来获得。
- 传播路径的第一阶段从根到达目标父节点,这被称为涓滴阶段(TrickleDown)
- 传播路径的最后一个阶段从目标父节点上升到根节点,这被称为冒泡阶段(BubbleUp)
- 而事件目标.Target位于传播路径的中间
大多数事件类型都会发送到传播路径上的所有元素。但是,某些事件类型会跳过冒泡阶段阶段。此外,某些事件类型仅发送到事件目标。
如果隐藏或禁用某个元素,则不会接收事件,但事件仍然传播到隐藏或禁用元素的父项或子项。
当事件沿传播路径发送时,Event.currentTarget将更新为当前处理事件的元素。这意味着在事件回调函数中,Event.currentTarget是回调被注册Event.target的元素,并且是发生事件的元素。
调度事件类型的行为
每种事件类型都有自己的调度行为。下表总结了每个事件类型在三列中的行为:
- 涓滴阶段:这种类型的事件在涓滴阶段被发送到元素
- 冒泡阶段:这种类型的事件在冒泡阶段被发送到元素
- 可取消(Cancellable):此类事件可以取消,停止或阻止其默认操作执行
涓滴阶段 | 冒泡阶段 | 撤销 | |
---|---|---|---|
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 | ✔ | ✔ | ✔ |
响应事件
有两种方式可以让VisualElement对收到的事件作出响应:
- 注册事件回调
- 实施默认操作
选择注册事件回调还是实施默认操作取决于很多因素。
- 如果,希望VisualElement接受到事件以特定方式作出反应,那么唯一的选择就是注册回调。
- 如果,希望VisualElement的所有实例都以相同的方式作出反应,那就为该类执行默认操作(视为特定类型的元素在收到事件时应具有的行为。例如,复选框应通过切换其状态来响应单击事件。应通过覆盖默认操作虚函数而不是在所有复选框上注册回调来实现此行为)。
区别是:
- 回调必须在类的实例上注册;默认操作在类上实现虚函数
- 回调对传播路径上的所有元素执行;默认操作仅对事件目标执行
可以通过调用event.PreventDefault()来阻止执行默认操作。某些事件类型无法取消,这意味着无法取消其默认操作,可以通过调用event.StopPropagation()或event.StopImmediatePropagation()甚至可以对不可取消的事件来阻止回调执行。
将行为实现为默认操作的其他好处是,它们的执行不需要在回调注册表中进行查找,并且没有回调的实例会在涓滴/冒泡传播过程中进行优化。
略(就是有关默认操作修改涓滴和冒泡阶段来方式默认操作的事件不会传播到父级)
https://docs.unity3d.com/Manual/UIE-Events-Handling.html
为了更好的可扩展性和更好的性能,请尽量避免在类的每个实例上注册回调
注册事件回调
注册事件回调的优点是,它允许自定义现有类的单个实例的行为。注册事件回调的缺点是性能较差,因为执行时间较长,因为每当收到事件时,都会检查注册的事件应该执行哪个回调。
//注册函数
myElement.RegisterCallback<MouseDownEvent>(MyCallback);
myElement.RegisterCallback<MouseDownEvent, MyType>(MyCallbackWithData, myData);
//回调
void MyCallback(MouseDownEvent evt) { /* ... */ }
void MyCallbackWithData(MouseDownEvent evt, MyType data) { /* ... */ }
//删除回调
myElement.UnregisterCallback()
为了避免两次执行已注册的回调,请使用optional RegisterCallback指定在哪个阶段执行回调。
默认情况下,在目标阶段和冒泡阶段执行已注册的回调,该默认行为可确保父元素在其子元素之后作出反应。例如,如果您希望父级首先做出反应,要覆盖其子级的行为,则应使用以下TrickleDown.TrickleDown选项注册回调:
myElement.RegisterCallback<MouseDownEvent>(MyCallback, TrickleDown.TrickleDown);
这通知调度程序在目标阶段和涓滴阶段执行回调。
实施默认操作
这会涉及到定义新元素,这在2019.1的文档中已经更新了,下次会说。
合成事件
在合成和发送自定义事件之前,应该了解UIElement如何分配和发送操作系统事件。UIElements使用事件池来避免重复分配事件对象,要合成并发送自己的事件,应该按照相同的步骤分配和发送事件:
- 从事件池中获取事件对象
- 填写活动属性
- 将事件包含在using块中以确保将其返回到事件池
- 将其传递给element.SendEvent()
如果要发送通常来自操作系统的事件,例如键盘事件和某些鼠标事件,请使用UnityEngine.Event初始化UIElements事件。
以下示例演示了如何合成和发送事件:
void SynthesizeAndSendKeyDownEvent(IPanel panel, KeyCode code,
char character = '\0', EventModifiers modifiers = EventModifiers.None)
{
// Create a UnityEngine.Event to hold initialization data.
// Also, this event will be forwarded to IMGUIContainer.m_OnGUIHandler
var evt = new Event() {
type = EventType.KeyDownEvent,
keyCode = code,
character = character,
modifiers = modifiers
};
using (KeyDownEvent keyDownEvent = KeyDownEvent.GetPooled(evt))
{
panel.SendEvent(keyDownEvent);
}
}
事件类型
本主题提供每种事件类型的摘要,有关每个活动成员及其用途的完整说明,请参阅API
捕获事件 IMouseCaptureEvent
MouseCaptureEvent
- MouseCaptureEvent:当元素捕获鼠标时发送
- target:捕获的元素
MouseCaptureOutEvent
- MouseCaptureOutEvent:在元素释放或以其他方式丢失鼠标捕获时发送
- target:丢失捕获的元素
改变事件 IChangeEvent
ChangeEvent
ChangeEvent< T >是元素值更改时发送的通用事件,这通常在控件更改时发送。对于InputEvent控件,不会为控件中的每个输入事件发送此事件,而是仅在值更改时发送。这通常是控件失去焦点或Enter按下键时。
- < T > :值的类型
- target:发生值变化的元素
- previousValue:旧的控制值
- newValue:新的控制值
命令事件 ICommandEvent
- target:键盘焦点的元素。如果没有元素具有焦点,则为null
- commandName:用于验证或执行的命令
ValidateCommandEvent
此事件由IMGUI发送,同时确定该命令是否由面板中的元素处理
ExecuteCommandEvent
当面板中的元素执行命令时,IMGUI将发送此事件
拖放事件
拖放操作期间发送的事件
DragExitedEvent
拖放操作已取消,放置目标不接受拖动元素
DragUpdatedEvent
拖动的元素在放置目标上移动
DragPerformEvent
拖动的元素被放置在接受它们的目标上,拖放操作现在已完成
DragEnterEvent
拖动的元素进入了新的放置目标
DragLeaveEvent
拖动的元素退出放置目标区域
布局事件
GeometryChangedEvent
当元素的位置或尺寸发生变化时发送的事件,此类事件仅发送到事件目标,不会传递。
- target:具有新的位置和尺寸
- oldRect:旧的位置和尺寸
- newRect:新的位置和尺寸
焦点事件 IFocusEvent
这些事件在给定元素或失去键盘焦点时发送。有两组焦点事件:
- FocusOutEvent和FocusInEvent在焦点改变发生之前沿着传播路径发送
- FocusEvent和BlurEvent仅在焦点更改后立即发送事件到目标
FocusOutEvent
当元素即将失去焦点时发送的事件
- target:将失去焦点的元素
- relatedTarget:将获得焦点的元素
FocusInEvent
当元素即将获得焦点时发送的事件
- target:将失去焦点的元素
- relatedTarget:将获得焦点的元素
BlurEvent
在元素失去焦点后发送的事件
- target:将失去焦点的元素
- relatedTarget:将获得焦点的元素
FocusEvent
在元素获得焦点后发送的事件
- target:将失去焦点的元素
- relatedTarget:将获得焦点的元素
输入事件
InputEvent
将数据输入到可视元素(通常是控件)时发送的事件,此事件不同于ChangeEvent,即使控件的值未更改,也会为控件中的每个输入事件发送该事件。
- target:发生输入变化的元素
- previousData:旧数据
- newData:新数据
键盘事件 IKeyboardEvent
KeyDownEvent
用户按下键盘上的键时发送的事件。
- target:具有键盘焦点的元素,如果没有元素具有键盘焦点,则这是该面板的根元素
KeyUpEvent
用户在键盘上释放键时发送的事件。
- target:具有键盘焦点的元素,如果没有元素具有键盘焦点,则这是该面板的根元素
鼠标事件 IMouseEvent
当元素捕获鼠标时,鼠标事件仅发送到捕获元素,不会传播。
MouseDownEvent
用户按下鼠标按钮时发送的事件
- target:如果一个元素捕获了鼠标,则这就是该元素。否则,这是光标下最顶部的可选元素
MouseUpEvent
用户释放鼠标按钮时发送的事件
- target:如果一个元素捕获了鼠标,则这就是该元素。否则,这是光标下最顶部的可选元素
MouseMoveEvent
当用户移动鼠标时,事件开始
- target:如果一个元素捕获了鼠标,则这就是该元素。否则,这是光标下最顶部的可选元素
WheelEvent
用户激活鼠标滚轮时发送的事件
- target:如果一个元素捕获了鼠标,则这就是该元素。否则,这是光标下最顶部的可选元素
MouseEnterWindowEvent
鼠标进入窗口时发送的事件
- target:如果一个元素捕获了鼠标,则这就是该元素。否则,这是光标下最顶部的可选元素
MouseLeaveWindowEvent
鼠标离开窗口时发送的事件
- target:如果一个元素捕获了鼠标,则这就是该元素。否则,这是光标下最顶部的可选元素
MouseEnterEvent
鼠标进入元素或其子项之一时发送的事件。此事件与MouseOverEvent的不同之处在于此事件会被发送到鼠标进入的每个元素,此事件不会传播。
- target:鼠标光标下的元素或其子项之一
MouseLeaveEvent
鼠标离开元素或其子项之一时发送的事件。此事件与MouseOutEvent的不同之处在于此事件会被发送到鼠标退出的每个元素,此事件不会传播。
- target:鼠标光标下的元素或其子项之一
MouseOverEvent
鼠标进入元素时发送的事件。此事件与MouseEnterEvent的不同之处在于此事件只会被发送到鼠标进入的元素,此事件不会传播。
- target:目前鼠标光标下的元素
MouseOutEvent
鼠标离开元素时发送的事件。此事件与MouseLeaveEvent的不同之处在于此事件只会被发送到鼠标退出的元素,此事件不会传播。
- target:目前鼠标光标下的元素
ContextualMenuPopulateEvent
ContextualMenuManager当需要使用菜单项填充上下文菜单时发送的事件
- target:正在为其构建上下文菜单的元素
面板事件
AttachToPanelEvent
当元素附加到面板后立即发送事件,由于设置面板是递归的,因此元素的所有后代也会接收事件
- target:目标被附加到面板
DetachFromPanelEvent
当元素从面板分离后立即发送事件,由于设置面板是递归的,因此元素的所有后代也会接收事件
- target:目标从面板上分离
工具栏事件
TooltipEvent
在显示工具栏之前发送的事件,处理程序应该设置TooltipEvent.tooltip的字符串和TooltipEvent.rect。
- target:需要显示工具提示的元素
IMGUI事件
IMGUIEvent
用于封装IMGUI特定事件的事件
超级简单栗子一枚
是我之前的简单练习1
//随便声明的结构体
struct Msg
{
public int a;
public int b;
public Msg(int num)
{
a = num;
b = num;
}
}
TemplateContainer uxmlVE;
public override VisualElement CreateInspectorGUI()
{
//...略
//找出Button
var myButton = uxmlVE.Query<Button>().First();
Msg m = new Msg(0);
//注册回调
myButton.RegisterCallback<MouseCaptureEvent,Msg>(MyCallback, m);
//...略
}
void MyCallback(MouseCaptureEvent evt,Msg m)
{
uxmlVE.Query<TextField>("text").First().value = "2";
Debug.Log(m.a);
Debug.Log(m.b);
}
那么这样就是到这里为止,最简单的注册事件回调就好了,之后会介绍如何实施默认操作。
目前Unity官方已经更新了2019.1的新文档,基本没有太多内容的改变,只有在UXML定义新元素有更新,这会在下一次一起介绍。