Unity2019 UIElement 笔记(十三)自定义VisualElement

    xiaoxiao2022-07-07  202

    本文来自https://docs.unity3d.com/Manual/UIE-UXML.html 版本2019.1

    定义新元素

    UIElements是可拓展的,用户可以自定义UI组件和元素,但是在使用UXML定义新元素之前,必须先从VisualElement或其子类中派生新类,然后在这个新类中实现适当的函数,这个新类必须实现默认的构造函数。 如下,派生新的StatusBar类并实现其默认构造函数:

    class StatusBar : VisualElement { public StatusBar() { m_Status = String.Empty; } string m_Status; public string status { get; set; } }

    为了使UIElements能够在读取UXML数据是实例化这个新类,需要为这个新类定义工厂(Factory)。除非你的工厂需要做一些特殊的事情,否则可以从UxmlFactoy< T >中找到工厂。并且建议将工厂类放在组件类中。 如下,演示了如何为StatusBar类通过UxmlFactoy< T >定义它的工厂且这个工厂名为Factory:

    class StatusBar : VisualElement { public class Factory : UxmlFactory<StatusBar> {} // ... }

    通过定义该工厂,就可以在UXML中使用StatusBar这个元素

    定义元素的属性

    你可以为新类定义UXML特征,并且设置它的工厂使用这些特征。 如下,演示了如何定义UXML特征来将status属性初始化为StatusBar类的属性,status属性是从XML数据中初始化的。

    class StatusBar : VisualElement { public class Factory : UxmlFactory<StatusBar, Traits> {} public class Traits : VisualElement.UxmlTraits { UxmlStringAttributeDescription m_Status = new UxmlStringAttributeDescription { name = "status" }; public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription { get { yield break; } } public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); ((StatusBar)ve).status = m_Status.GetValueFromBag(bag, cc); } } // ... }

    使用UxmlTraits有两个目的:

    使用工厂来初始化新创建的对象通过分析模式生成过程来获取元素的信息,这个信息将转换成XML架构的指令

    上面的代码样例执行了以下行为:

    m_Status的声明定义了一个名为status的XML属性uxmlChildElementsDescription返回一个空的IEnumerable,这表明StatusBar没有子项Init()函数从XML解析器中在属性包中读取status的属性值,并将该值设置为StatusBar.status通过在StatusBar类中放置UxmlTraits类来允许Init()函数访问私有成员StatusBar新的UxmlTraits类继承自基类UxmlTraits,因此它共享基类的属性Init()调用base.Init()初始化基类属性

    上面的代码样例声明了一个字符串属性通过UxmlStringAttributeDescription类。 UIElements支持以下类型的属性,每个属性都将C#类型链接到XML类型:

    UxmlStringAttributeDescription:属性值是一个字符串UxmlFloatAttributeDescription:属性值必须是C#float类型范围内的单精度浮点值。UxmlDoubleAttributeDescription:属性值必须是C#double类型范围内的双精度浮点值。UxmlIntAttributeDescription:属性值必须是C#int类型范围内的整数值。UxmlLongAttributeDescription:属性值必须是C#long类型范围内的长整数值。UxmlBoolAttributeDescription:属性值必须为 true或false。UxmlColorAttributeDescription:属性值必须是表示color(#FFFFFF)的字符串。UxmlEnumAttributeDescription< T >属性值必须是表示该Enum类型的值之一的字符串T。

    要让元素接受任何类型的子元素,必须重写uxmlChildElementsDescription属性。例如,要让StatusBar接受任何类型子元素的元素, uxmlChildElementsDescription必须按如下方式指定属性

    public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription { get { yield return new UxmlChildElementDescription(typeof(VisualElement)); } }

    定义命名空间

    在C#中定义了新元素之后,就可以在UXML中使用该元素,如果在新的命名空间中定义了新元素,就应该为这个命名空间定义前缀。命名空间前缀被声明为这个根节点< root >元素的属性,并且会在检查元素的时候替代完整的命名空间名称。

    如果想要直接使用,那么只要放在UnityEditor.UIElements或者UnityEngine.UIElements命名空间下即可。

    要定义命名空间前缀,请在程序集中为每个要定义的命名空间前缀添加UxmlNamespacePrefix属性。

    [assembly: UxmlNamespacePrefix("My.First.Namespace", "first")] [assembly: UxmlNamespacePrefix("My.Second.Namespace", "second")]

    这可以在程序集的任何C#文件的根级别(任何命名空间之外)完成。 模式生成系统执行以下操作:

    检查这些属性并使用它们生成架构。< UXML >在新创建的UXML文件中添加名称空间前缀定义作为元素的属性包括其xsi:schemaLocation属性中命名空间的模式文件位置。

    之后应该更新项目的UXML架构。选择Asset > Update UIElements Schema以确保文本编辑器识别新元素。

    高级使用

    自定义UXML名称

    可以自定义UXML名称通过重写IUxmlFactory.uxmlName和IUXmlFactory.uxmlQualifiedName属性。确保uxmlName在命名空间中是唯一的,uxmlQualifiedName在项目中是唯一的。如果两个名称都不唯一,则在尝试加载程序集时会引发异常。 以下代码示例演示如何覆盖和自定义UXML名称:

    public class FactoryWithCustomName : UxmlFactory<..., ...> { public override string uxmlName { get { return "UniqueName"; } } public override string uxmlQualifiedName { get { return uxmlNamespace + "." + uxmlName; } } }

    为元素选择工厂

    默认情况下,会通过IUxmlFactory实例化一个元素并使用元素的名称选择元素。可以通过重写IUXmlFactory.AcceptsAttributeBag来使得选择过程会参考属性值。工厂会检查元素属性,以确定是否可以为UXML元素实例化对象。 这非常有用,例如,你的VisualElement是通用的。在这种情况下,你的类的序列化工厂会检查XML中type属性的值,根据值,可以选择是否实例化。 在多个工厂可以实例化元素的情况下,选择第一个注册的工厂。

    重写基类属性的默认值

    你能改变在一个基类中声明的属性的默认值,通过设置其派生类UxmlTraits中的defaultValue。 例如,以下代码展示了如何更改m_TabIndex的默认值:

    class MyElementTraits : VisualElement.UxmlTraits { public MyElementTraits() { m_TabIndex.defaultValue = 0; } }

    接受任何属性

    默认情况下,生成的XML架构声明了元素可以具有任何属性。 那些不在UxmlTraits类中声明的属性值不收到限制,这与XML验证器形成对比,后者检查声明的属性的值是否与其声明匹配。 附加属性包含在IUxmlAttributes传递给IUxmlFactory.AcceptsAttributBag()和IUxmlFactory.Init()函数。是否使用这些附加属性取决于工厂实现。默认行为时放弃附加属性。 这意味着那些额外的属性并没有附加到实例化的VisualElement上并且那些属性不能通过UQuery进行查询。 定义新元素时,可以通过UxmlTraits构造函数将接受的属性限制为显示声明的属性通过将UxmlTraits.canHaveAnyAttribute属性设置为false。


    在我们创建好了自己的类之后,下面就是补充说明之前的实施默认操作。

    实施默认操作

    默认操作适用于该类的所有实例。这意味着为元素接收的每个事件调用一个或两个方法,而实现默认操作的类也可以在其实例上注册回调。 事件类在处理事件之前或之后执行行为,是通过事件类重写EventBase类的PreDispatch()方法来实现的。由于事件是队列,这些方法能够在处理事件时更新状态或者执行任务,而不是在事件发出时。例如,在BlurEvent改变聚焦元素之前改变当前元素。 实现默认操作需要从VisualElement派生一个子类,并且实现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()中实现默认操作。

    案例

    实际上将上面默认操作的代码放入StatusBar类中,就已经有了默认操作的响应,这里就不做演示了


    小结

    那么到此为止,有关UIElement的内容就都介绍完毕了,如果有空的话,我会写一个简单的案例。目前的话,我已经在使用UIElement了,讲道理,的确是比之前的IMGUI方便了不少,真的非常推荐大家使用!

    最新回复(0)