Unity 6.3
0 онлайн 106 гостей 3 в системе
Вход
UI системы Шаг 213 из 317

Определение источника данных для привязки времени выполнения

При создании объекта привязки необходимо определить источник данных. Источником данных является объект, содержащий свойство, к которому требуется привязать объект. В качестве источника данных для привязки во время выполнения можно использовать любой объект C#.

Чтобы разрешить системе привязки доступ к источнику данных, вы должны определить свойство dataSource объекта привязки к объекту источника данных. Например, если у вас есть объект источника данных и элемент UI, как показано ниже:

using UnityEngine;
using UnityEngine.UIElements;
using Unity.Properties;

public class DataSource
{
    [CreateProperty]
    public Vector3 vector3 { get; set; } 
}

Примечание: атрибут CreateProperty обязателен, чтобы свойство можно было привязать к источнику данных.

Добавить элемент UI, например:

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 Toolkit использует Unity.Properties модуль для создания сумки свойства для связывания данных между двумя объектами. Он генерирует пакеты свойств на основе имеющейся информации типа C#. Однако для некоторых встроенных типов Unity генерируемый пакет свойств может не содержать ожидаемых свойств. Это может произойти, когда этим типам не хватает необходимых атрибутов. Например, Rect тип имеет открытые свойства и закрытые поля, не помеченные [SerializeField], или вы определяете поля на стороне нативного кода, которые не могут быть определены во время выполнения.

Примечание: При использовании типа значения в качестве источника данных возникает стоимость блокирования из-за того, что VisualElement.dataSource определяется как свойство объекта. Это означает, что тип значения должен быть блокирован до назначения его свойству dataSource. Операция блокирования вводит накладные расходы на выделение и копирование памяти, что приводит к потере производительности. Это влияние на производительность может быть незначительным для небольших наборов данных или случайного использования. Однако в сценариях, требующих высокой производительности, или при работе с большими объемами данных стоимость блокирования может стать проблемой.

Чтобы определить источник данных для привязок времени выполнения, а также для целей авторства или серийного кодирования, используйте общий шаблон:

using UnityEngine;
using Unity.Properties;

public class PropertyBagExample : MonoBehaviour
{
    // Serialization 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 the backing field of an auto-property. 
    // Note that the name of the auto-property is postfixed with k_BackingField.
    [field: SerializeField, DontCreateProperty]
    [CreateProperty]
    public float floatValue { get; set; }
}

Примечание: Эти связывающие свойства по своей природе обладают полиморфизмами.

Интеграция контроля версий и отслеживания изменений

Чтобы повысить производительность, вы можете интегрировать версионирование и отслеживание изменений в источник данных для привязки. По умолчанию система привязки непрерывно запрашивает источник данных и обновляет UI при каждом изменении, не зная, изменилось ли что-либо с момента последнего обновления. Хотя этот подход удобен для простых проектов, он не эффективно масштабируется, когда речь идет о многочисленных привязках.

Управление версиями и отслеживание изменений для источников являются дополнительными функциями, которые требуют специальной активации. По умолчанию активные объекты привязки обновляются каждый кадр, что может быть ресурсоемким процессом. Чтобы свести к минимуму накладные расходы на обработку, существует два интерфейса, которые можно реализовать для указания системе привязки, когда обновлять привязки, связанные с источником:

  • IDataSourceViewHashProvider интерфейс предоставляет хеш-код представления, показывающий, когда нужно обновить все привязки, связанные с источником.
  • INotifyBindablePropertyChanged интерфейс позволяет получать уведомления об изменении каждого свойства и обновлять только те привязки, которые связаны с изменившимся свойством.

Эти интерфейсы могут быть реализованы по отдельности или вместе для обеспечения большего контроля.

Примечание: сейчас типы, реализующие любой из этих интерфейсов, автоматически включаются в генерацию кода, когда сборка помечена [assembly: Unity.Properties.GeneratePropertyBagsForAssembly]Однако, это поведение может измениться.

Внедрение IDataSourceViewHashProvider

Чтобы предоставить хеш-код представления для конкретного источника, реализуйте IDataSourceViewHashProvider интерфейс. Он позволяет системе привязок пропускать обновление отдельных объектов привязки, если источник не менялся с прошлого обновления.

В следующем примере создается источник данных, который немедленно сообщает об изменениях:

using System;
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 интерфейс также буферизует изменения. Это особенно полезно, когда данные меняются часто, а интерфейсу не нужно немедленно отражать каждое изменение.

Для буферизации изменений, реализуйте 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

Чтобы уведомить связывающую систему об изменениях в конкретных свойствах, реализуйте INotifyBindablePropertyChanged интерфейс. Когда вы его реализуете, система привязок обновляет только соответствующие привязки при обнаружении изменения по пути свойства. Например, если сообщается об изменении value свойства, система привязки обновляет все привязки, связанные с путями источника данных, имеющими value prefix. Другие объекты привязки, связанные с источником, остаются неизменными.

Этот подход позволяет высокоэффективно обновлять UI, так как система привязки выполняет минимальную работу.

В следующем примере создается источник данных, который уведомляет об изменениях по каждому свойству:

using System;
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 интерфейс, система привязок не выполняет проверок, пока ей не сообщат об изменении. Если об изменении не сообщить, система не обновит связанные с этим свойством привязки. Поэтому сообщайте об изменениях только тогда, когда это действительно нужно.
  • INotifyBindablePropertyChanged отличается от CallbackEventHandler.NotifyPropertyChanged. Когда вы используете VisualElement или его производные классы (например, BaseField) в качестве источника данных, вы можете сообщать об изменениях через CallbackEventHandler.NotifyPropertyChanged. Нет необходимости реализовывать INotifyBindablePropertyChanged.

Внедрить IDataSourceViewHashProvider и INotifyBindablePropertyChanged

Для достижения оптимальной производительности привязки реализуйте интерфейсы IDataSourceViewHashProvider и INotifyBindablePropertyChanged. Система привязки отслеживает изменения свойств до тех пор, пока не изменится хэш-код представления. В этот момент она эффективно обновляет только затронутые привязки, связанные с измененными свойствами.

Это требует дополнительного кодирования boilerplate, но обеспечивает максимальную гибкость и преимущества производительности.

В следующем примере создается источник данных, реализующий оба интерфейса. Источник данных уведомляет систему привязки о произошедшем изменении. Однако вместо немедленного обновления привязок, обновления задерживаются до вызова метода 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. Для связывания с полями или свойствами делегирования используйте оператор = вместо операторов += или -=. Если вам нужно добавить или удалить делегатов вместо назначения их, возможно, потребуется реализовать пользовательский тип привязки.

Интерфейсы

Как упоминалось в static types разделе вам необходимо создать экземпляр объекта для источника данных. Система привязок работает с интерфейсами, но типы, реализующие интерфейс со свойствами, помеченными [CreateProperty] Для каждого типа вы должны пометить его поля и свойства соответственно, чтобы сделать их привязными. Это ограничение будет устранено в будущем выпуске.

Встроенные компоненты и объекты

Процесс создания пакета свойств в C# в первую очередь предназначен для работы с пользовательскими типами. В результате, в настоящее время существует ограниченная поддержка встроенных компонентов и объектов Unity. Это объясняется различными факторами, включая поля встроенных типов, которые определяются в собственном коде, явную обработку серийного кодирования движком или отсутствие атрибута [SerializeField]. Однако поля и свойства из пользовательских компонентов и скриптовых объектов работают как ожидалось.

Это ограничение будет устранено в будущем выпуске. Тем временем, есть два возможных решения:

  • Чтобы открыть поле или свойство встроенного базового класса, добавьте private свойство в своём классе, чтобы открыть его системе привязок.
  • Чтобы использовать поле или свойство из встроенного типа, например Transform, создайте тип оболочки, который раскрывает требуемые свойства.

Дополнительные ресурсы