I’m posting this both to ask a couple of questions, and to put up an example that I couldn’t find. I have a working example of and almost codeless Editor Window with a ListView that binds to a ScriptableObject list of custom objects. I’ll post the (long) example first and put the questions at the end.
- Create a ScriptableObject as the data source
- Right-click on the directory where you want to create the ScriptableObject, and Select Create > Scriptable Objects > <ScriptableObject Name> (e.g. TaskDataSO)
using System;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "TaskDataSO", menuName = "Scriptable Objects/TaskDataSO")]
public class TaskDataSO : ScriptableObject
{
public List<Task> taskList = new();
[Serializable]
public class Task
{
public string taskName;
public bool completed;
}
}
- Create an Item Template
- Select Window > UI Toolkit > UI Builder
- Select the UXML document in the Hierarchy
- Check Document Settings > Editor Extension Authoring
- At the top of the Viewport, using the Theme dropdown, select Active Editor Theme
- Drag and drop the Visual Element you want to bind
- Click the 3 dots next to the Value field and select Add Binding…
- In Data Source Path, type the name of the field you want to bind (e.g. taskName)
- Using the Binding Mode dropdown, select Two Way
- Click Add Binding
- Ignore the Console spam (The spam will stop once you close the UI Builder)
- Repeat for each Visual Element that you want to bind
- Click the 3 dots next to the Value field and select Add Binding…
- At the top of the Viewport, using the File dropdown, select Save
- NOTE: The UXML namespace may be ui or engine. It seems that if you create the UXML document by using File > New from the UI builder, you get ui and if you right-click in the Unity editor to create the UXML document, you get engine. It seems to work either way as long as you are consistent. I don’t know what the difference is.
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:TextField label="Task Name">
<Bindings>
<ui:DataBinding property="itemsSource" data-source-path="taskName" binding-mode="TwoWay" />
</Bindings>
</ui:TextField>
<ui:Toggle label="Completed">
<Bindings>
<ui:DataBinding property="value" data-source-path="isCompleted" binding-mode="TwoWay" />
</Bindings>
</ui:Toggle>
</ui:UXML>
- Create the ListView UI
- Select Window > UI Toolkit > UI Builder
- At the top of the Viewport, using the File dropdown, select New
- Select the uxml document in the Hierarchy
- Check Document Settings > Editor Extension Authoring
- At the top of the Viewport, using the Theme dropdown, select Active Editor Theme
- Drag and drop the List View you want to bind
- Under Bindings > Data Source, make sure that Object is selected and in the object field, select the ScriptableObject that you created in step 1.1
- Using the Virtualization Method dropdown, select Dynamic Height
- Check Show Add Remove Footer
- Using the Binding Source Selection Mode dropdown, select Auto Assign
- In the Item Template object field, select the Item Template that you created in step 2 (e.g. TaskListItemTemplate)
- At the top of the Viewport, using the File dropdown, select Save
- Drag the UXML Preview at the bottom up to expand the pane and click the open icon in the upper right corner to open the UXML in your IDE (NOTE: Make sure the namspace on the UXML tags is the same as in the Item Template created in step 2, or it will not work)
- Locate the ListView tag (may be in the ui or engine namespace)
- Delete the closing slash at the end of the tag
- Create a closing tag for the ListView tag:
</ui:ListView>or</engine:ListView>depending on which namespace the UI Builder picked (NOTE: make sure you match the namespace to the rest of the tags or it will not work)
- Above the closing tag you just added, paste these UXML tags
- Replace <Name of the List in the ScriptableObject> with the name of the list you declared in your ScriptableObject (e.g. taskList)
- Save the UXML file
<Bindings>
<engine:DataBinding property="itemsSource" data-source-path="<Name of the List in the ScriptableObject>" binding-mode="TwoWay" />
</Bindings>
Here is the whole ListView UXML file (NOTE: The IDs will be different for you):
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:ListView data-source="project://database/Assets/ScriptableObjects/TaskDataSO.asset?fileID=11400000&guid=63a1b58cf6ade3f409acec511a95a017&type=2#TaskDataSO" item-template="project://database/Assets/UI/TaskListItemTemplate.uxml?fileID=9197481963319205126&guid=3000deaa4c6f91049a7e703c2a621c4f&type=3#TaskListItemTemplate">
<Bindings>
<ui:DataBinding property="itemsSource" data-source-path="taskList" binding-mode="TwoWay" />
</Bindings>
</ui:ListView>
</ui:UXML>
- Create the EditorWindow script
- Create a folder named Editor (EditorWindow scripts need to be in a folder named Editor in order to work)
- Right click on the Editor folder and select Create > Scripting > MonoBehaviour Script
- Name the script what you want (e.g. TaskListEditorWindow)
- Open the file in your IDE
- Paste in this code and save the file:
using System.IO;
using UnityEditor;
using UnityEngine.UIElements;
public class TaskListEditorWindow : EditorWindow
{
private TaskDataSO vortexToDoData;
private VisualTreeAsset vortexToDoSettingsUIVisualTreeAsset;
[MenuItem("Tools/Task List")]
public static void OpenWindow()
{
GetWindow<TaskListEditorWindow>("Task List");
}
private void OnEnable()
{
LoadTaskListViewUI();
}
private void LoadTaskListViewUI()
{
string TaskListUIVisualTreeAssetFilename = "TaskListViewUI";
string[] guids = AssetDatabase.FindAssets($"{TaskListUIVisualTreeAssetFilename} t:VisualTreeAsset");
if (guids.Length != 1)
{
throw new FileNotFoundException($"Could not find {TaskListUIVisualTreeAssetFilename}.uxml");
}
string vortexToDoSettingsUIVisualTreeAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
this.vortexToDoSettingsUIVisualTreeAsset =
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(vortexToDoSettingsUIVisualTreeAssetPath);
VisualElement rootVisualElement = this.rootVisualElement;
TemplateContainer vortexToDoSettingsUITemplateContainer = this.vortexToDoSettingsUIVisualTreeAsset.Instantiate();
rootVisualElement.Add(vortexToDoSettingsUITemplateContainer);
}
}
- Testing
- Right click on the ScriptableObject (e.g. TaskDataSO) and select Properties
- In the Unity Editor, select Tools > Task List
- In the newly opened Task List Editor Window, click on the + button (This will generate Console spam until data is entered)
- You should see that a new record was created in both the Task List Editor Window, and the Properties window
- Type in a Task Name
- Click Completed
- You should see the changes reflected in the Properties window
- Any changes you make in the Properties window should be reflected in the Task List Editor Window
- Repeat as many times as desired
- In the newly opened Task List Editor Window, click on the + button (This will generate Console spam until data is entered)
So, my questions are:
- Why do you get a different namespace in the UXML depending on if you right click to create it (engine) or if you create it in the UI Builder (ui)?
- How can I get rid of the console spam when creating a new record (empty Task object) in the bound ListView? e.g.:
[UI Toolkit] Could not retrieve the value at path ‘[3].taskName’ for source of type ‘List`1’: the path from the source to the target is either invalid or contains a null value. (Builder)
UnityEditor.RetainedMode:UpdateSchedulers ()
[UI Toolkit] Could not retrieve the value at path ‘[3].isCompleted’ for source of type ‘List`1’: the path from the source to the target is either invalid or contains a null value. (Builder)
UnityEditor.RetainedMode:UpdateSchedulers ()
I hope this helps someone. Thanks!







