Binding to items in ListView and MultiColumnListView with Unity 6 and UI Builder with Runtime UI Toolkit

With Unity 6 being recently released with its improvements to UI Toolkit, I decided to take the plunge and start learning to use the UI Builder.

I can say one of the issues I am having in general is that Unity 6 is so recently released, most of the information I can find is for older versions and I’m not sure how accurate it is anymore.

I come from a C# with WPF background, so I am trying to work with splitting code from UI as much as possible. As such I am trying to do as much binding I can in the UI Builder and leave as little as possible to be handled by code.

I am running into the following 2 problems:

1.) I can’t seem to bind the items to a ListView or MultiColumnListView using the UI Builder, and have to set the ItemsSource directly in code and then call RefreshItems(). Is there a way to just bind a list to the ListView or MultiColumnListView from within the UI Builder?

2.) BindingPath for the columns in a MultiColumnListView seem to be doing nothing, and for every column, the entire row item is being bound to each column, regardless of the settings I set for the columnb BindingPath, regardless of whether it is a valid field to the list item, an invalid name, or blank. I can make a cell template and bind say a label text to a field I want, but that would mean I need a cell template for every cell, which will be unmanageable with many columns and many lists, especially when I just want basic text displayed for each.

Here is some relevant code:

namespace Assets.Scripts.Menus.ViewModels
{
    abstract class ViewModelBase : ScriptableObject, INotifyBindablePropertyChanged
    {
        public event EventHandler<BindablePropertyChangedEventArgs> propertyChanged;
        protected void NotifyChanged([CallerMemberName] string propertyName = "") =>
            propertyChanged?.Invoke(this, new BindablePropertyChangedEventArgs(propertyName));
    }
}
namespace Assets.Scriptables.Test
{

    [Serializable]
    public class SourceData
    {
        public int number;
        public string text;
        public float fnumber;
        public override string ToString() =>
            "SouceData ToString: "+text + ", " + number + ", " + fnumber;
    }

    [CreateAssetMenu]
    class TestVm : ViewModelBase
    {
        

        public List<SourceData> items = new()
        {
            new SourceData(){number = 10, fnumber = 5.5f, text = "one"},
            new SourceData(){number = 200, fnumber=-1.0f, text = "two"},
            new SourceData(){number = -5, fnumber = 3.33333f, text="three"},
            new SourceData(){number = 0, fnumber = 0, text="four"},
            new SourceData(){number = 3, fnumber = 22.2f, text="five"},
            new SourceData(){number = -100, fnumber = 15.75f, text="six"},
            new SourceData(){number = 15, fnumber = 193.2f, text="seven"},
            new SourceData(){number = 2, fnumber = 1234f, text="eight"},
            new SourceData(){number = -1, fnumber = 50000f, text="nine"}
        };
    }
}
class ListViewTester : MonoBehaviour
{
    UIDocument _Doc;
    ListView _View;
    MultiColumnListView _MCView;

    public TestVm vm;
    void Start()
    {
        _Doc = GetComponent<UIDocument>();
        _View = _Doc.rootVisualElement.Q<ListView>("listView");
        _MCView = _Doc.rootVisualElement.Q<MultiColumnListView>();
        Button b = _Doc.rootVisualElement.Q<Button>("ListViewButton");
        b.clicked += B_clicked;
        b = _Doc.rootVisualElement.Q<Button>("MultiColumnListViewButton");
        b.clicked += B_clicked1; ;

    }

    private void B_clicked1()
    {
        _MCView.itemsSource = vm.items;
        _MCView.RefreshItems();
    }

    private void B_clicked()
    {
        _View.itemsSource = vm.items;
        _View.RefreshItems();
    }
}

The MultiColumnListView in the UI Builder:

And the result:

You need to assign Cell Template. It should be contain text element. Then, add binding like this on text field:

then bind all collumns.

            _teamListView = _teamPanelRoot.Q<MultiColumnListView>("TeamListView");

            foreach (var item in _teamListView.columns)
            {
                item.bindCell = (element, i) => // item is template
                {
                    if (gameManager.PlayerTeam == null)
                        return;
                    element.dataSource = gameManager.PlayerTeam.AllPlayers[i].Player;
                    element.dataSourcePath = new();
                };

            }

            _teamListView.itemsSource = gameManager.PlayerTeam.AllPlayers as IList;

I would prefer not to use cell templates, as my use case will have several MultiColumnListViews, some with numerous columns.

It seems the default behavior for the columns is to present a Label with the results of the ToString() method for the bound value. This would be perfect if I could bind to the fields of the object for the row, but it is binding the entire object for every column, which seems incorrect.

What does the Binding Path for the column do, if not specify what field to use for the value in that column?

For me, I use template because I would have different cell template. E.g

  • col 1 contain: text field
  • col 2 contain: text field, vector3 field, image
  • col 3 contain: matrix4, text field, button

Then I add binding to the template.

If you want to make your own cell in code, then you need also make Column and add it to MultiColumnListView.

Or you can use columns[name/index].makeCell and columns[name/index].bindCell if you already created column on UI Builder.

I’m not really sure what you mean.

It’s path to your value.

So my test list items are:

[Serializable]
public class SourceData
{
    public int number;
    public string text;
    public float fnumber;
    public override string ToString() =>
        "SouceData ToString: "+text + ", " + number + ", " + fnumber;
}

and when I create the columns in UI Builder, all of the cells display the ToString() result for that particular row, eg:

SourceData ToString: one, 10, 5.5

for the first row in the screenshot above.

My expectation would be that when I provide a Binding Path for a column, it would use that field of the SourceData instance of the row for that cell, but that is not what happens. Regardless of what I put for Binding Path for the row, even blank or invalid values, the cells are alway filled with the ToString result of the row’s SourceData instance.

But where is that value passed? It’s not used for the default label created if there is no cell template, as shown above. How do you use that value?

Hi, sorry for such delay answer.

Based on doc the samples of bindingPath was for Editor. Tbh, I did try remove bindingPath my runtime MultiColumnListView and the binding still works. I believe because I’m using item template where I assign the binding for each property of the template + set Binding Source Selection to Auto.

e.g change this Manual to Auto.

TLDR how to make working MultiListViewColumn

  • Create Column in UI Builder
  • Assign template to each column e.g template that contain text
  • In template, make text binding set to your dataSourceClass.value
  • Set Binding Source Selection to Auto in MultiListViewColumn.
  • Assign item source in C# e.g listView.itemsSource = myList

Is it possible to bind data in editor? I’d like to work with MultiColumnListView and be able to show rows/cells while I work without the need of running the game, is that possible somehow? any docs?

Is it possible to bind data in editor? I’d like to work with MultiColumnListView and be able to show rows/cells while I work without the need of running the game, is that possible somehow? any docs?

Yes, it’s possible. If it is strictly related to the editor and it’s backed by serialized data, I would advise to use the editor bindings. Here is an example using a ListView, which should be similar for the MultiColumnListView.

If it is not strictly for editor or if it is not backed by serialized data, then you can use the runtime bindings. Here is an example using a ListView.

Note that both the editor and runtime bindings are using the same underlying system, where the runtime binding system is more generalized and the editor binding system uses a custom binding that will also support undo/redo, prefab workflows, etc. as long as the data is serializable.

Hope this helps!

listView.SetBinding(“itemsSource”, new DataBinding() {dataSourcePath = new PropertyPath(“items”)});
How to set this in addBinding of item Template in UI Builder? I set the value of Data SourcePath to items, but it doesn’t work.

itemsSource is not exposed in the UI Builder inspector at the moment because IList is not a valid serializable type for a UXML Atrribute.

We have plans to display bindable, but not serializable properties in the UI Builder, but for now, you would need to add the binding to the UXML file manually.