Version: 2023.2
Language : English
Get started with runtime binding
Define a data source for runtime binding

Create a runtime binding in C# scripts

To bind a property of a visual elementA node of a visual tree that instantiates or derives from the C# VisualElement class. You can style the look, define the behaviour, and display it on screen as part of the UI. More info
See in Glossary
to a data source in C#, create an instance of DataBinding. With this binding type, you can define a dataSource and a dataSourcePath directly on the binding instance.

Basic workflow

To create a runtime binding in C#, follow these steps:

  1. Create a binding. Bindings are objects that you can create, register, or unregister to a visual element through a unique ID.
  2. Define the data source and the data source path for the binding object. The data source is the object that contains the property you want to bind to. The data source path is a relative path from the data source to the property you want to bind to.
  3. Define the binding mode and update triggers for the binding object. The binding mode defines how changes are replicated between the data source and the UI(User Interface) Allows a user to interact with your application. Unity currently supports three UI systems. More info
    See in Glossary
    . The update trigger defines when to update the binding object.
  4. Register the binding object to the visual element.
  5. If necessary, add type converters to convert data types between the data source and the UI.

The following example created a binding object and registers it to a visual element. It’s equivalent to this UXML in the Get started with runtime binding example:

var dataSource = ScriptableObject.CreateInstance<ExampleObject>();

var root = new VisualElement
{
    name = "root",
    dataSource = dataSource
};

var vector3Field = new Vector3Field("Vec3 Field");

vector3Field.SetBinding("label", new DataBinding
{
    dataSourcePath = new PropertyPath(nameof(ExampleObject.vector3Label)),
    bindingMode = BindingMode.ToTarget
});

vector3Field.SetBinding("value", new DataBinding
{
    dataSourcePath = new PropertyPath(nameof(ExampleObject.vector3Value))
});

root.Add(vector3Field);

var floatField = new FloatField("Float Field") { value = 42.2f };

floatField.SetBinding("value", new DataBinding
{
    dataSourcePath = new PropertyPath(nameof(ExampleObject.sumOfVector3Properties))
});

root.Add(floatField);

var label = new Label("Label")
{
    dataSourcePath = new PropertyPath(nameof(ExampleObject.dangerLevel))
};

// Here, we do not need to set the dataSourcePath because we will only use two bindings and they will use the same path,
// so we set the dataSourcePath on the Label directly instead.
var binding = new DataBinding
{
    bindingMode = BindingMode.ToTarget
};

// Add a custom float -> string converter
binding.sourceToUiConverters.AddConverter((ref float v) => 
{
    return v switch
    {
        >= 0 and < 1.0f/3.0f => "Danger",
        >= 1.0f/3.0f and < 2.0f/3.0f => "Neutral",
        _ => "Good"
    };
});

// Add a custom float -> StyleColor
binding.sourceToUiConverters.AddConverter((ref float v) => new StyleColor(Color.Lerp(Color.red, Color.green, v)));

// Since the binding is targeting the same data source property, we can re-use the same instance.
label.SetBinding("text", binding);
label.SetBinding("style.backgroundColor", binding);

root.Add(label);

Register and unregister binding objects

You can use the following methods to manage binding objects:

Report a change

You can create the bindable properties in the same way as other data sources, which means that you can also use VisualElement types as data sources. The main difference between a VisualElement type and other data sources is that VisualElement types come with built-in versioning. You must use the built-in versioning of a VisualElement type to propagate changes.

To report a change, call the NotifyPropertyChanged method. This method takes a BindingId that identifies the property that changed. The following example shows how to report a change:

// Creates a static readonly BindingId that is unique to this type. This is used to identify the property. 
public static readonly BindingId intValueProperty = nameof(intValue);

private int m_IntValue;

[CreateProperty]
public int intValue
{
    get => m_IntValue;
    set
    {
        if (m_IntValue == value)
            return;
        m_IntValue = value;
        
        // This instructs the binding system that a change occured.
        NotifyPropertyChanged(intValueProperty);
    }
}

Best practices

Follow these tips and best practices to optimize performance:

  • Use correct binding IDs: The binding system uses the binding ID to identify the binding object and the target property of the element. The binding ID must be the target property of the element. For example, if you want to bind to the value property of a Vector3Field, the binding ID must be Vector3Field.valueProperty.
  • Avoid internal data updates with bindings: Don’t use bindings to update the internal data of a visual element. For example, don’t use bindings to synchronize the x, y, and z sub-elements for a Vector3Field. Instead, use a binding to synchronize the value property of the Vector3Field with a Vector3 property of a data source.

Known limitations

UI Toolkit doesn’t report changes in element.style and element.resolvedStyle. Therefore, you can use binding instances to target the resolved style of an element but can’t track changes to them.

Additional resources

Get started with runtime binding
Define a data source for runtime binding