
此文介绍UI Toolkit中事件传播的机制和处理方法,目的是了解Unity此类按钮实现背后的事件机制。
简单介绍
在UI Toolkit中,鼠标键盘输入等事件称为事件,按钮等交互式UI就是通过处理这个事件来制作的。
首先,创建一个EditorWindow,如下所示。
一个绿色的子元素(Child)放在红色的根元素(Root)下面,一个蓝色的元素(Grandchild)作为子元素放置。

也可以使用UXML或者USS来组装,不过这次是代码,代码如下:
use UnityEditor;
use UnityEngine;
use UnityEngine.UIElements;
public seal class EventExample : EditorWindow
{
private void OnEnable()
{
var root = rootVisualElement;
root.style.paddingTop = 10 ;
root.style.paddingBottom = 10 ;
root.style.paddingLeft = 10 ;
root.style.paddingRight = 10 ;
root.style.backgroundColor = new StyleColor( new Color( 0.8f , 0.3f , 0.3f ));
var child = new visual elements
{
style=
{
fill top= 10 ,
fill bottom= 10 ,
fill left= 10 ,
fill right= 10 ,
height = new StyleLength(Length.Percent( 100 )),
backgroundColor = new StyleColor( new Color( 0.3f , 0.8f , 0.3f ))
}
};
var grandchild = new VisualElement
{
style=
{
fill top= 10 ,
fill bottom= 10 ,
fill left= 10 ,
fill right= 10 ,
height = new StyleLength(Length.Percent( 100 )),
backgroundColor = new StyleColor( new Color( 0.3f , 0.3f , 0.8f ))
}
};
child. add (grandson);
root. add(child);
}
[MenuItem( "Window/EventExample" )]
public static void ShowWindow()
{
GetWindow<EventExample>( "EventExample" );
}
}
注册事件回调
接下来,当鼠标在元素上按下时,为上一节中创建的子元素注册一个回调,要注册回调,请使用 ,指定要接收的事件类型。
grandchild.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log( "mouse down on grandchild" );
});
当鼠标在元素上按下时,已经注册了一个事件,可以看到点击蓝色元素会输出日志。
接下来,为第一个父元素的红色元素以相同的方式注册一个鼠标按下事件。
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log( $"mouse down on child" );
});
现在,如果在此状态下再次将鼠标按到蓝色元素上,将调用child两个事件回调。

也就是说,如果按下第一个子元素,就可以看到父元素中的元素的事件也被调用了。
然而,在视觉上,只是点击了蓝色元素,所以想控制它只有蓝色元素(子)响应。如果想练习这种控制,需要准确了解事件传播在UI工具包中的工作原理。
事件传播的概念
在UI Toolkit中,当从操作系统接收到鼠标按下事件时,该事件首先发送到根可视化元素。然后它被传播给它的子对象,然后再传播给它的子对象。这样,事件从根元素传递到终端元素之前的元素的阶段称为涓流阶段。

然后该事件被传播到终端元素(目标),这称为目标阶段。
目标不一定是终端元素,但在mousedown事件的上下文中它将是终端元素。

最后,事件从终端元素之前的第一个元素传播到根元素,这称为起泡阶段。

检查事件传播的行为
现在,可以在事件的回调中检索此事件传播信息,如下所示:
element.RegisterCallback<MouseDownEvent>(x =>
{
x.propagationPhase; // propagation stage
x.target; // target (terminal element)
x.currentTarget; // the element currently handling the event
});
因此,为了实现上述只想对视觉点击的元素做出反应,需要将事件传播阶段的感知过程改写如下:
grandchild.RegisterCallback<MouseDownEvent>(x =>
{ if (x.propagationPhase != PropagationPhase.AtTarget)
return ;
Debug.Log( "mouse down on grandchild" );
});
child.RegisterCallback<MouseDownEvent>(x =>
{ if (x.propagationPhase != PropagationPhase.AtTarget)
return ;
Debug.Log( "mouse down on child" );
});
现在,视觉上只有被点击的元素会做出反应。
另一方面,如上所述,对于目标元素以外的元素,事件将被处理两次:trickle-down phase和bubble-up phase。
RegisterCallback在该方法中,可以在第二个参数中指定要处理的阶段,默认情况下,它接收冒泡阶段事件,如果指定TrickleDown参数,它接收滴流阶段事件。
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log( "mouse down on child" );
}, TrickleDown.TrickleDown);
停止事件的传播
还可以通过使用StopPropagation在中间停止事件传播
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log( "Mouse down on child and stop propagation" );
x.StopPropagation();
});
但是,在这种情况下,在同一时间注册的所有事件将按如下方式调用。
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log("mouse down on child 1 and stop propagation");
x.StopPropagation();
});
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log("mouse down on child 2"); // This will be called even if you stop the propagation
});
如果不想调用同时注册的任何事件,请改用StopImmediatePropagation。
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log("mouse down on child 1 and stop immediate propagation");
x.StopImmediatePropagation(); // StopImmediatePropagation
});
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log("mouse down on child 2"); // no longer called
});
打算在另一篇文章中写到它,但是即使以这种方式停止传播,仍然会执行定义为每个UI元素的默认操作的处理,PreventDefault用于避免默认操作处理。
child.RegisterCallback<MouseDownEvent>(x =>
{
Debug.Log( "Mouse down on child and prevent default" );
x.PreventDefault();
});
总结
至此,已经总结了事件在UI Toolkit中是如何工作的,以及如何注册回调注册来处理它们,
不过如果事件回调要处理的内容比较复杂,还有一个机制叫Manipulator,在下一篇文章中会介绍相关内容。
…
以上是关于UI Toolkit中的事件传播机制和处理方法的全部内容,如果你有任何反馈,请随时在本页面下方留言。