ListView seems completely broken

I’ve just reported a bug about ListView, but I’m kinda surprised, because what I thought would be a complicated repro case ended up being very simple, and the bug seems to be present on 2022 and 2023.

My script is this:

public class TestScript : MonoBehaviour
{
    public string[] strings;
}

And the editor is this:

[CustomEditor(typeof(TestScript))]
public class TestScriptEditor : Editor
{
    public override VisualElement CreateInspectorGUI()
    {
        var root = new VisualElement();
        var listView = new ListView();

        root.Add(listView);

        listView.BindProperty(serializedObject.FindProperty(nameof(TestScript.strings)));
        return root;
    }
}

If I add the TestScript to a GameObject, the result is:

The full stack trace is this:

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
System.Collections.Generic.List1[T].get_Item (System.Int32 index) (at <1334a096378c46d2bd2be2db1a37116e>:0) UnityEngine.UIElements.VerticalVirtualizationController1[T].ReleaseItem (System.Int32 activeItemsIndex) (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.FixedHeightVirtualizationController`1[T].Resize (UnityEngine.Vector2 size) (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.BaseVerticalCollectionView.Resize (UnityEngine.Vector2 size) (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.BaseVerticalCollectionView.PostRefresh () (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.BaseListView.PostRefresh () (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.BaseVerticalCollectionView.RefreshItems () (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEditor.UIElements.Bindings.ListViewSerializedObjectBinding.SetBinding (UnityEngine.UIElements.ListView targetList, UnityEditor.UIElements.Bindings.SerializedObjectBindingContext context, UnityEditor.SerializedProperty prop) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.ListViewSerializedObjectBinding.CreateBind (UnityEngine.UIElements.ListView listView, UnityEditor.UIElements.Bindings.SerializedObjectBindingContext context, UnityEditor.SerializedProperty prop) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.SerializedObjectBindingContext.BindListView (UnityEngine.UIElements.ListView listView, UnityEditor.SerializedProperty prop) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.SerializedObjectBindingContext.CreateBindingObjectForProperty (UnityEngine.UIElements.VisualElement element, UnityEditor.SerializedProperty prop) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.SerializedObjectBindingContext.BindPropertyRelative (UnityEngine.UIElements.IBindable field, UnityEditor.SerializedProperty parentProperty) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.SerializedObjectBindingContext.BindTree (UnityEngine.UIElements.VisualElement element, UnityEditor.SerializedProperty parentProperty) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.SerializedObjectBindingContext.ContinueBinding (UnityEngine.UIElements.VisualElement element, UnityEditor.SerializedProperty parentProperty) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.SerializedObjectBindingContext.Bind (UnityEngine.UIElements.VisualElement element) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEditor.UIElements.Bindings.DefaultSerializedObjectBindingImplementation+BindingRequest.Bind (UnityEngine.UIElements.VisualElement element) (at <6b1405e82d454258a8fbde4cb0621937>:0)
UnityEngine.UIElements.VisualTreeBindingsUpdater.Update () (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.VisualTreeUpdater.UpdateVisualTreePhase (UnityEngine.UIElements.VisualTreeUpdatePhase phase) (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.Panel.UpdateBindings () (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.UIElementsUtility.UnityEngine.UIElements.IUIElementsUtility.UpdateSchedulers () (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEngine.UIElements.UIEventRegistration.UpdateSchedulers () (at <5566f9e97c3b47c3802abbb5405c3a92>:0)
UnityEditor.RetainedMode.UpdateSchedulers () (at <6b1405e82d454258a8fbde4cb0621937>:0)

I looked at the code in a debugger, and it’s a simple thing where it tries to remove the current elements when rebuilding, but there are none to remove, so it breaks.

The same bug happens if I use a bindingPath and bind the SerializedObject, and if I create a ListView by loading a uxml file.

In addition to this slightly annoying bug, I have a worse one where if I go to debug mode and add some values:

9557371--1351204--upload_2024-1-2_9-44-52.png

The result when I switch back to non-debug mode is:

9557371--1351207--upload_2024-1-2_9-45-21.png

So it can’t even display the elements correctly!

Is the ListView not ready to use? This set of bugs is so severe that I don’t quite understand how they happened for a production ready product.

Directly binding to a collection property hasn’t ever really seemed to work for me, as it seems to try and include the array size as an element in the list???

Last time I used a ListView I had to go through this sorta rigmarole (example from a scene organiser tool of mine):

private void BuildSceneGroupsDisplay()
{
    _sceneGroupsContainer.Clear();

    var serialisedSettings = new SerializedObject(_settings);
    _groupListProperty = serialisedSettings.FindProperty("_sceneGroups");
    _groupListView = new ListView()
    {
        showBoundCollectionSize = false,
        reorderable = false,
        selectionType = SelectionType.None,
        virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight
    };
    _groupListView.makeItem += MakeSceneGroupElement;
    _groupListView.destroyItem += RemoveSceneGroupElement;
    _groupListView.bindItem += BindSceneGroup;
    _groupListView.BindProperty(_groupListProperty);
    _sceneGroupsContainer.Add(_groupListView);
}

private SceneOrganiserGroupVisualElement MakeSceneGroupElement()
{
    var group = new SceneOrganiserGroupVisualElement();
    _groupElements.Add(group);
    return group;
}

private void RemoveSceneGroupElement(VisualElement ve)
{
    var groupElement = (SceneOrganiserGroupVisualElement)ve;
    _groupElements.Remove(groupElement);
}

private void BindSceneGroup(VisualElement ve, int index)
{
    var groupElement = (SceneOrganiserGroupVisualElement)ve;
    var property = _groupListProperty.GetArrayElementAtIndex(index);
    groupElement.BindSceneGroupProperty(property);
}

Namely bit I imagine to be important is the BindSceneGroup method, that specifically pulls the right serialised properties from the collection, and then I manually bind it to my custom visual element.

No errors but it’s a lot of code, so I do wonder if this is the expected workflow? If so it could really use some streamlining.

This was my first time using ListView for an editor purpose, admittedly.

Thanks! Is keeping the list of group elements necessary for that functionality, or are you using it somewhere else?

I found an update for my issue - if I include the foldout header (which includes the number field), the bugs go away.
If I’m to guess, a bunch of the code assumes that the number field is present, and skips it in order to get to the correct index.

I was using them elsewhere so I don’t think it’s necessary.