定义新元素
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方便了不少,真的非常推荐大家使用!