定义新元素

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有两个目的:

  1. 使用工厂来初始化新创建的对象
  2. 通过分析模式生成过程来获取元素的信息,这个信息将转换成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方便了不少,真的非常推荐大家使用!