Version: 2021.3+
This example demonstrates how to bind a custom control to a native Unity type.
The example creates a custom control that displays a 2D image. It uses the custom control in a custom Editor for a new asset type, and it binds the custom control to a field in an asset of the new type.
You can find the completed files that this example creates in this GitHub repository.
This guide is for developers familiar with the Unity Editor, UI Toolkit, and C# scripting. Before you start, get familiar with the following:
BindableElement
INotifyValueChanged
Create a serialized object that contains a Texture2D.
Create a Unity project with any template.
In the Project window, create a folder named bind-custom-control
to store the files.
Create a C# script named TextureAsset.cs
and replace its contents with the following:
using UnityEngine;
namespace UIToolkitExamples
{
[CreateAssetMenu(menuName = "UIToolkitExamples/TextureAsset")]
public class TextureAsset : ScriptableObject
{
public Texture2D texture;
public void Reset()
{
texture = null;
}
}
}
Create a custom control with C# that represents a reference to a 2D texture asset, and style it with USS.
Editor
.TexturePreviewElement.cs
.TexturePreviewElement.cs
with the following:using System;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
namespace UIToolkitExamples
{
public class TexturePreviewElement : BindableElement, INotifyValueChanged<Object>
{
public new class UxmlTraits : BindableElement.UxmlTraits { }
public new class UxmlFactory : UxmlFactory<TexturePreviewElement, UxmlTraits> { }
public static readonly string ussClassName = "texture-preview-element";
Image m_Preview;
ObjectField m_ObjectField;
Texture2D m_Value;
public TexturePreviewElement()
{
AddToClassList(ussClassName);
// Create a preview image.
m_Preview = new Image();
Add(m_Preview);
// Create an ObjectField, set its object type, and register a callback when its value changes.
m_ObjectField = new ObjectField();
m_ObjectField.objectType = typeof(Texture2D);
m_ObjectField.RegisterValueChangedCallback(OnObjectFieldValueChanged);
Add(m_ObjectField);
styleSheets.Add(Resources.Load<StyleSheet>("texture_preview_element"));
}
void OnObjectFieldValueChanged(ChangeEvent<Object> evt)
{
value = evt.newValue;
}
public void SetValueWithoutNotify(Object newValue)
{
if (newValue == null || newValue is Texture2D)
{
// Update the preview Image and update the ObjectField.
m_Value = newValue as Texture2D;
m_Preview.image = m_Value;
// Notice that this line calls the ObjectField's SetValueWithoutNotify() method instead of just setting
// m_ObjectField.value. This is very important; you don't want m_ObjectField to send a ChangeEvent.
m_ObjectField.SetValueWithoutNotify(m_Value);
}
else throw new ArgumentException($"Expected object of type {typeof(Texture2D)}");
}
public Object value
{
get => m_Value;
// The setter is called when the user changes the value of the ObjectField, which calls
// OnObjectFieldValueChanged(), which calls this.
set
{
if (value == this.value)
return;
var previous = this.value;
SetValueWithoutNotify(value);
using (var evt = ChangeEvent<Object>.GetPooled(previous, value))
{
evt.target = this;
SendEvent(evt);
}
}
}
}
}
In the Editor folder, create a folder named Resources
.
In the Resources folder, create a StyleSheet named texture_preview_element.uss
and replace its contents with the following:
.texture-preview-element {
width: 200px;
height: 200px;
}
.texture-preview-element > .unity-image {
flex-grow: 1;
}
Create the Inspector UI, and bind the custom control to the Texture2D object.
In the Editor folder, create a C# script named TextureAssetEditor.cs
and replace its contents with the following:
using UnityEditor;
using UnityEngine.UIElements;
using UnityEngine;
namespace UIToolkitExamples
{
[CustomEditor(typeof(TextureAsset))]
public class TextureAssetEditor : Editor
{
[SerializeField]
VisualTreeAsset m_VisualTree;
public override VisualElement CreateInspectorGUI()
{
return m_VisualTree.CloneTree();
}
}
}
Create a UXML file with a TexturePreviewElement, and set the binding-path
property to texture
. This binds the TexturePreviewElement to TextureAsset.texture
.
In the Editor folder, create a UI Document named texture_asset_editor.uxml
and replace its contents with the following:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:example="UIToolkitExamples" editor-extension-mode="True">
<example:TexturePreviewElement binding-path="texture" />
</ui:UXML>
In the Project window, select TextureAssetEditor.cs.
Drag texture_asset_editor.uxml to Visual Tree in the Inspector.
TextureAsset
.TextureAsset.texture
changes.