创建绑定对象时,必须定义数据源。数据源是包含要绑定到的属性的对象。您可以将任何C#对象用作运行时绑定数据源。
为了使绑定系统访问数据源,必须将绑定对象的 dataSource
属性定义为数据源对象。例如,如果您有一个数据源对象和类似的UI元素:
using UnityEngine;
using UnityEngine.UIElements;
using Unity.Properties;
public class DataSource
{
public Vector3 vector3 { get; set; }
}
var element = new VisualElement();
然后您可以定义element.dataSource
属性到数据源对象如下:
element.dataSource = new DataSource();
这使应用于元素的绑定能够访问DataSource
目的。
为了使应用于元素的绑定能够访问vector3
领域DataSource
对象,添加以下内容:
element.dataSourcePath = PropertyPath.FromName(nameof(DataSource.vector3));
启用将绑定应用于子元素以访问vector3
领域DataSource
对象,添加以下内容:
var child = new VisualElement();
child.dataSourcePath = PropertyPath.FromName(nameof(DataSource.vector3));
element.Add(child)
UI工具包使用unity.properties
要创建的模块属性包用于绑定两个对象之间的数据。它根据可用的C#类型信息生成属性袋。但是,对于某些内置的统一类型,生成的特性袋可能不包含预期的属性。当这些类型缺乏必要的属性时,可能会发生这种情况。例如,Rect
类型具有不归因于公共属性和私人字段[SerializeField]
,或者您定义本机侧的字段,该字段在运行时无法确定。
注意 :当您使用值类型作为数据源时,由于VisualElement.Datasource
被定义为对象属性。这意味着您必须在将值类型分配给dataSource
财产。拳击操作引入了内存分配和复制的开销,从而导致性能成本。这种性能影响对于小型数据集或偶尔使用可能并不重要。但是,在关键表演的情况下或处理大量数据时,拳击成本可能会成为一个问题。
要定义用于运行时绑定的数据源以及为创作或序列化目的定义的,请使用公共模式,如下所示:
using UnityEngine;
using Unity.Properties;
public class MyBehaviour : MonoBehaviour
{
// Serializations go through the field.
[SerializeField, DontCreateProperty]
private int m_Value;
// Bindings go through the property rather than the field.
// This allows you to do validation, notify changes, and more.
[CreateProperty]
public int value
{
get => m_Value;
set => m_Value = value;
}
// This is a similar example, but for an auto-property.
[field: SerializeField, DontCreateProperty]
[CreateProperty]
public float floatValue { get; set; }
}
注意 :这些可结合特性本身具有多态性特征。
为了提高性能,您可以集成版本控制并将跟踪更改为绑定数据源。默认情况下,绑定系统连续进行轮询数据源并在每个修改时更新UI,而不知道自上次更新以来是否实际发生了变化。尽管这种方法对于简单的项目很方便,但是当它处理众多绑定时,它不会有效地扩展。
源的版本控制和更改跟踪是需要故意激活的可选功能。默认情况下,主动绑定对象都会更新每个帧,这可能是资源密集的过程。为了最大程度地减少处理开销,您可以实现两个接口来指示绑定系统何时更新与源相关的绑定:
IDataSourceViewHashProvider
接口提供了一个 view hash code 来指示何时更新链接到源的所有绑定。INotifyBindablePropertyChanged
接口启用每个属性的更改通知,以仅针对与已修改的属性相关的单个绑定触发更新。您可以分别或共同实现这些接口以进行更大的控制。
注意 :当前,在汇编标记时,实现的类型会自动选择进入代码生成[assembly:Unity.Properties.GeneratePropertyBagsForAssembly]
。但是,这种行为可能会改变。
IDataSourceViewHashProvider
要为特定来源提供视图哈希代码,请实现idatasourceViewHashProvider
界面。该接口使绑定系统能够跳过更新某些绑定对象,如果源自上次更新以来没有更改。
以下示例创建了一个数据源,该数据源立即发生变化:
using UnityEngine.UIElements;
public class DataSource : IDataSourceViewHashProvider
{
public int intValue;
public float floatValue;
// Determines if the data source has changed. If the hash code is different, then the data source
// has changed and the bindings are updated.
public long GetViewHashCode()
{
return HashCode.Combine(intValue, floatValue);
}
}
IDataSourceViewHashProvider
接口还可以缓冲更改。当数据经常更改时,此缓冲功能特别有用,但是UI不需要立即反映所有更改。
要缓冲区更改,请在要通知绑定系统时,实现 idatasourceViewHashProvider
接口并调用 CommitChanges
方法。
默认情况下,如果其数据源的版本保持不变,则绑定系统不会更新绑定对象。但是,即使您调用其 MarkDirty
方法或将updateTrigger
设置为 BindingUpdateTrigger.EveryFrame
,即使版本未更改或将绑定对象更改。当您使用 IDataSourceViewHashProvider
进行缓冲区更改时,请避免源中的任何结构性更改,例如从列表中添加或删除项目,或更改子场或子专业的类型。
以下示例创建了一个数据源,该数据源可以更改:
using UnityEngine.UIElements;
public class DataSource : IDataSourceViewHashProvider
{
private long m_Version;
public int intValue;
public void CommitChanges()
{
++m_Version;
}
// Required by IDataSourceViewHashProvider
public long GetViewHashCode()
{
return m_Version;
}
}
InotifyBindablePropertychanged
要通知有关特定属性更改的绑定系统,请实现InotifyBindable Propertychanged
界面。实现此接口时,绑定系统仅在沿属性路径检测到更改时仅更新相关的绑定。例如,如果对MyAwesomeObject
属性,绑定系统更新与具有数据源路径相关的所有绑定MyAwesomeObject
前缀。与源相关的其他绑定对象仍然不受影响。
这种方法可以对UI进行高效的更新,因为绑定系统执行最少的工作。
以下示例创建了一个数据源,该数据源通知每个属性的变化:
using System.Runtime.CompilerServices;
using Unity.Properties;
using UnityEngine.UIElements;
public class DataSource : INotifyBindablePropertyChanged
{
private int m_Value;
// Required by INotifyBindablePropertyChanged
public event EventHandler<BindablePropertyChangedEventArgs> propertyChanged;
[CreateProperty]
public int value
{
get => m_Value;
set
{
if (m_Value == value)
return;
m_Value = value;
Notify();
}
}
void Notify([CallerMemberName] string property = "")
{
propertyChanged?.Invoke(this, new BindablePropertyChangedEventArgs(property));
}
}
注意:实现INotifyBindablePropertyChanged
接口,绑定系统在通知更改时不会执行检查。不报告更改意味着绑定系统不会更新与该属性相关的绑定。因此,请确保您仅在必要时报告更改。
idatasourceViewHashProvider
和 InotifyBindablePropertychanged
为了实现最佳的绑定性能,请同时实现IDataSourceViewHashProvider
和INotifyBindablePropertyChanged
接口。绑定系统跟踪更改属性,直到视图的哈希代码更改为止。在这一点上,它仅有效地更新了与更改属性相关的受影响的绑定。
这需要其他样板代码,但提供了最大的灵活性和性能优势。
以下示例创建了实现两个接口的数据源。当发生更改时,数据源通知绑定系统。但是,保存更新,而不是立即更新绑定,直到Publish()
调用方法。当您处理高度挥发性数据时,这种方法特别有用,在此过程中,更新UI每个帧都会产生性能成本。
using System;
using System.Runtime.CompilerServices;
using Unity.Properties;
using UnityEngine.UIElements;
public class DataSource : IDataSourceViewHashProvider, INotifyBindablePropertyChanged
{
private long m_ViewVersion;
private int m_Value;
private int m_OtherValue;
public event EventHandler<BindablePropertyChangedEventArgs> propertyChanged;
[CreateProperty]
public int value
{
get => m_Value;
set
{
if (m_Value == value)
return;
m_Value = value;
Notify();
}
}
[CreateProperty]
public int otherValue
{
get => m_OtherValue;
set
{
if (m_OtherValue == value)
return;
m_OtherValue = value;
Notify();
}
}
public void Publish()
{
++m_ViewVersion;
}
public long GetViewHashCode()
{
return m_ViewVersion;
}
void Notify([CallerMemberName] string property = "")
{
propertyChanged?.Invoke(this, new BindablePropertyChangedEventArgs(property));
}
}
遵循以下技巧和最佳实践来优化性能:
将C#属性用于可绑定的属性 :当您定义可绑定的属性时,请使用C#属性而不是字段。这为合并验证,通知或任何自定义行为提供了灵活性,从而产生了更健壮和可维护的代码。
避免在C#属性中进行大量计算 :如果属性需要大量处理,则仅在必要时执行计算,然后使用缓存值进行后续绑定。
避免不必要的通知 :在值没有实际变化时,要谨慎通知更改。如果值保持不变,则不必发送通知。
实现版本控制和更改跟踪 :在您的数据源中使用版本控制。为了获得最佳性能,请同时使用版本控制和更改跟踪。
将数据源用作数据和UI之间的缓冲区 :只要可能,请在数据和UI之间实现数据源,而不是直接使用数据。这种方法提供了几个好处:
更好地控制数据流,并促进来自UI的跟踪变化。它允许您管理何时以及如何更新数据。
将所有UI数据集中到一个位置,简化数据访问并降低整个应用程序的复杂性。
保持原始数据的清洁度和效率,从而消除了您类型上的额外仪器的需求并确保数据完整性。
以下部分概述了运行时绑定数据源的已知限制。
您不能将静态类型用作数据源。您必须为系统功能创建一个类型的实例。
为一种类型生成的属性袋仅考虑字段和属性。因此,您无法绑定到方法或内置事件。
但是,有可能绑定到诸如Action
或者Func
委托类型。要绑定到委托字段或属性,请使用=
运算符而不是+=
或者-=
。如果您需要添加或删除委托而不是分配委托,则可能需要实现自定义绑定类型。
如静态类型部分所述,您必须为数据源创建一个对象实例。虽然绑定系统与接口一起使用,但类型的类型与用[CreateProperty]
标记的属性实现接口。没有为它们自动生成的可绑定属性。对于每种类型,您必须分别标记其字段和属性以使其可约束。此限制将在以后的版本中解决。
C#中的属性袋生成过程主要旨在与用户定义类型一起使用。结果,目前对Unity的内置组件和对象的支持有限。这是由于各种因素,包括在本机代码中定义的内置类型的字段,发动机的明确序列化处理或缺少[SerializeField]
属性。但是,来自用户定义的组件和脚本对象的字段和属性按预期工作。
此限制将在以后的版本中解决。同时,有两种解决方法:
要从内置基类公开字段或属性,请添加一个private
您自己的班级中的财产将其暴露于绑定系统。
使用内置类型的字段或属性,例如Transform
,创建一种揭示所需属性的包装类型。