Can you use the new UI Toolkit Data Binding on List Views?

The new binding system looks great and there were a lot of clarifications in this thread already, but I haven’t found a way to bind a collection to a list view or similar, is there a way to do this?

Looking forward to the announcement post :slight_smile:

1 Like

Hi @SkywardAssembly !

Sorry, I didn’t catch this post quickly. So the announcement has been made here .

Regarding the ListView, I would say that it’s partially supported in the runtime bindings system. I say partially because you can use the DataBinding type to create bindings between your data and the bindable properties of the ListView, however the ListView requires a little bit more logic to function properly.

So the best way to use the new system with the ListView control is to do the following:

  • Create a VisualTreeAsset to contain the UI of the list item.
  • Set the asset created in 1. as the itemTemplate of the ListView. This is a new property that we added to make it easier to setup the ListView and to use the new binding system.
  • Set the bindingSourceSelectionMode to AutoAssign. What this will do is that it will set the itemsSource as the dataSource of each item and it will prefix the dataSourcePath with the correct index.

You can use bindings to set the itemsSource itself and the other various properties of the ListView. You should note however that the itemsSource property is not available in the UI Builder, so the binding must be manually added in the UXML file.

What makes the support partial is:

  • It won’t call the ListView.RefreshItems() or ListView.Rebuild() for you whenever the list size changes. Depending on what you are doing, this can be achieved by creating a CustomBinding that tracks the size of the itemsSource and call refresh whenever it changes.
  • If the ListView is refreshed or rebuilt after the binding operations has been completed, your ListView might be temporarily displaying outdated information.

This will be part of the “Current limitations & what’s next” part of the announcement, but I haven’t had time yet to write it.

Hope this helps!

3 Likes

Thank you for getting back to us!
We were able to create a custom element that handles the refresh and rebuild with this information

1 Like

Hi martinpa,

do you habe an example for that binding? I don’t get it to work. I tried it with code like following in a custom binding.

protected override void OnDataSourceChanged(in DataSourceContextChanged context)
    {
        Debug.Log("Context changed");
        var listview = context.targetElement as ListView;

        listview.itemsAdded += (obj) =>
        {
            Debug.Log("Item added");
            listview.Rebuild();
        };
        listview.itemsRemoved += (obj) =>
        {
            Debug.Log("Item removed");
            listview.Rebuild();
        };
    }

I get the “Context changed” message, but not the other two messages.

Same question! :slight_smile:

Hi @StefanDu , the OnDataSourceChanged will be called when the resolved data source itself has changed and not when its sub-values are changed. For the ListView’s callbacks, you will only get those when items are added or removed through the ListView itself. So if you are adding and removing items through the list directly, the callbacks won’t be called.

So, in your custom binding, you will need to track the list count to perform a refresh of the ListView when it has changed.

To make this automatic, we would need some concept of an observable list and a list view that can operate on those. Sadly, we do not have this at the moment.

Hope this helps!

Hi @StefanDu ,

I’ve attached a small, self-contained example of such a binding.

Hope this helps!

9440249–1324283–ListViewBinding.zip (57.9 KB)

3 Likes

Hello @martinpa_unity , thanks for the example, it was helpful.

I have found a bug related to it, I think. The example works fine, but when I implement INotifyBindablePropertyChanged in my data source it prevents calls to update on the ListViewCountTracker. Implementing IDataSourceViewHashProvider is fine and works. From what I see, all custom bindings should update every frame by default and the example doesn’t seem to be changing that. I haven’t try it on your whole example, just copied the ListViewCountTracker to my project and used it.

Hey @Knedlo , yes, I believe you may have found a bug. It seems that we are not checking that the updateTrigger is BindingUpdateTrigger.OnSourceChanged before skipping updates based on the changes reported by the INotifyBindablePropertyChanged interface.

I’ll get that fixed right away.

Thank you for reporting it.

Glad to help. Just in case anyone is looking for a workaround in the meantime… it is possible to dispatch the propertyChanged with empty string which results in an update of ListViewCountTracker (or any CustomBinding, I presume).

@martinpa_unity As you mentioned itemTemlate here: do you have any idea why itemTemplate forgets is VisualTreeAsset reference upon closing/re-opening Unity in the scenario described here?: https://discussions.unity.com/t/938212

To be clear: the uxml itself does not change, but the reference is reported as “null” by UI Builder.

Hi @Fribur , this looks like a bug, can you report it using “Help > Report a bug…” ?
Thanks!

Thanks and no. Most bug reports I open get closed, so do not see the point anymore. I guess it will be resolved eventually as this appears to be a pretty fundamental functionality. To reproduce just drag VisualElement onto the item-Template Field, save uxml, and observe what is saved (in Unity 2023.3.b05)

Hi and thank you for this response.

I’m creating a custom editor for a mission system and it implements a Listview for objectives (the class Mission holds a List). Objectives are its own ScriptableObject that contains a string, an int and an enum. It has its own custom editor script and UXML (binding paths are set to each of the properties in the UI Builder). In the general Mission custom editor (where the ListView is), I implement the previously mentioned UXML as itemTemplate in the ListView Control and set the binding mode to autoassign. I followed those 3 steps.

Thing is, the custom editor for the objectives itself works properly when I create one, the properties are shown and are in sync. The binding between the objective list property in my mission class and the ListView “works” in that it displays the correct number of items when I add an objective. The template is added and its shown in the inspector just how it is built in the UXML, however, its fields are not bound and reset upon changing focus in the inspector. When I change properties in the ScriptableObjects they are not changed in the elements shown in the Mission custom inspector and viceversa.

Do I need to do something else?

As something of note, Objective is an abstract class and what I add to the list in Mission is a child that derive from it.

EDIT: Now I have seen that the actual count of the ListView is 0 even though the binded list has elements and the template appears where the ListView control is. I’m adding elements to the list by calling a class method through target, could that be where the problem lies?

Hi @OuterGazer

Somehow, I missed your post, my apologies.

From you description, I’m not sure if you are using the runtime bindings or the editor bindings. The editor bindings is a specialized binding type that understands about undo/redo, SerializedProperty, etc. The runtime bindings is the more general system, which is not aware of the editor concepts.

Since you are dealing with custom editors, most likely, you should be using the editor bindings. If so, can you post your question in Unity Engine - Unity Discussions ?

I’m so confused, why can’t the listview just use Data Source Path in UI Builder? Instead of manually editing the xml and adding the thingy?

That is because we support multiple types of bindings, including user-defined binding types. We also support having multiple bindings on the same element targeting different properties, including style properties.

I understand that, but it feels as if this should be the basic default workflow that everyone expects:

  • Select List View
  • Select Data Source (scriptable object)
  • Select Data source path (filtered by list/array property type)
  • Assign uxml item template
  • Done
    If a user wants to provide additional bindings, or provide their own custom binding then they could drop down to uxml at that point?

Hi @hawaiian_lasagne ,

What you are suggesting is roughly how the editor bindings currently work (minus the data source part, since it’s implicit /set by the system in that case). One feedback we’ve often received from that way of doing is to be able to bind to other properties than to value.

The editor systems has several drawbacks:

  • You can only bind again value of an element implementing the INotifyValueChanged interface. In some cases, we can allow some other properties, but these are more or less hardcoded.
  • You cannot bind against any other property.
  • It can only bind against serialized data, which means limited data types.

Both systems supports relative and absolute binding paths and in both cases, if a binding was automatically created between value and the data source property, you would need to provide absolute paths for any other bindings you would want to use. This is not an issue with the editor bindings, as you cannot have multiple bindings anyway.

The way I see it, we have one system where “one way fits all” and is not extensible and one system where you have more control over what is happening and is extensible. You can build something in the new system that would emulate the old system (because that is precisely what we’ve done to the old system: it’s re-written in the new system as a custom binding), but the other way around is not possible.

To get the workflow you want, you could derive from ListView, override the makeItem and manually clone the itemTemplate, fetch the elements that have set the dataSourcePath and add a binding object targeting the proper property (value for BaseField<T>, text for Label, etc.)

Hope this helps!

Thanks for the reply @martinpa_unity

Ultimately I was looking for a way to bind the list view to a List<> field on a scriptable object, without any custom code (inheriting list view, manual modifying uxml)

The new binding system seems like it is almost there, but not quite.

And I think the old(?) editor binding system doesn’t support assigning a scriptable object to preview the behaviour in uibuilder - please correct me if I’m wrong.

I will stick with modifying the uxml for now as it’s working pretty nicely and gives a preview in uibuilder.

This is for a custom editor and not runtime, so the only thing I’m worried about is if I continue down this path, will I loose undo/redo support? Ideally I’m looking for a solution where I can assign a scriptable object, preview in uibuilder, and have undo/redo support.

Any further advice you have is much appreciated