Need help getting nested ListView bindings working correctly

Hi,

I’m trying to setup a nested ListView’s and have some issues with the bindings. Note that I’m on 2021 LTS so can’t use the new TreeView.

I have tried 2 different approaches but both have issues:

  1. I have tried to use set the ‘bindingPath’ for the parent and children ListViews. The top level one works fine but it doesn’t ever bind on the children.

  2. I have set the ‘bindingPath’ on the parent list but manually create the children (serializedProperty) array from the serializedObject bound to the root visual element. This works but does not update if the children array is modified via external actions (i.e. via OnSceneGUI). If the overlay is closed and re-opened the change is reflected

This serialzedObject contains a property as such:

[Serializable]
public class EnemySpawnGroup
{
    [SerializeField][HideInInspector] protected bool m_IsGroup;
    [SerializeField][HideInInspector] protected bool m_Foldout;
    [SerializeField][HideInInspector] protected EnemySpawn[] m_Items;

    public bool IsGroup => m_IsGroup;
}

[SerializeField] private EnemySpawnGroup[] m_EnemyList;

Here is a cut down version of the overlay code used.

[Overlay(typeof(SceneView), "Enemies Waves")]
public class EnemiesOverlay_Example : Overlay
{
    EnemyWaveInfo m_EnemyWaveInfo;
    private VisualTreeAsset m_GroupRowTemplate, m_ItemRowTemplate;
    private SerializedObject m_SerializedObject;
   
    public override VisualElement CreatePanelContent()
    {
        var root = new VisualElement { name = "Waves Root" };

        var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("path/to/Overlay.uxml");
        visualTree.CloneTree(root);

        m_GroupRowTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("path/to/GroupTemplate.uxml");
       
        m_ItemRowTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("path/to/ChildItemTemplate.uxml");
        
       var so = new SerializedObject(m_EnemyWaveInfo);
       root.Bind(so);
       m_SerializedObject = so;


        Example_BindGroupListView(root);
       
        return root;
    }
   
    private void Example_BindGroupListView(VisualElement el)
    {
        var listView = el.Q<ListView>("groupList");
        listView.bindingPath = "m_EnemyList";
        listView.makeItem = m_GroupRowTemplate.CloneTree;
       
        // EXAMPLE 1: This works but there are issues with modifications to the child list not updating correctly
        listView.bindItem = BindChildListElement_ManualItemSource;
       
        // EXAMPLE 1: This does not bind to the nested ListView elements
        listView.bindItem = BindChildListElement_WithBindingPath;
       
        listView.Rebuild();
    }

    private void BindChildListElement_ManualItemSource(VisualElement el, int groupIndex)
    {
        var enemyList = m_SerializedObject.FindProperty("m_EnemyList");
        var group = enemyList.GetArrayElementAtIndex(groupIndex);
        var children = group.FindPropertyRelative("m_Items");

        var items = new List<SerializedProperty>(children.arraySize);
        for (var j = 0; j < children.arraySize; j++)
        {
            items.Add(children.GetArrayElementAtIndex(j));
        }

        var listView = el.Q<ListView>("childList");
        listView.itemsSource = items;
        listView.makeItem = m_ItemRowTemplate.CloneTree;
        listView.bindItem = (element, itemIndex) => { Example_BindGroupRowElement_Manual(element, itemIndex, group); };
        listView.Rebuild();
    }
    private void Example_BindGroupRowElement_Manual(VisualElement el, int itemIndex, SerializedProperty property)
    {
        Debug.Log($"Example_BindGroupRowElement_Auto {el}, {itemIndex} {property}");
    }
   
    private void BindChildListElement_WithBindingPath(VisualElement el, int groupIndex)
    {
        var listView = el.Q<ListView>("childList");
        listView.bindingPath = "m_Items";
        listView.makeItem = m_ItemRowTemplate.CloneTree;
        listView.bindItem = Example_BindGroupRowElement_BindPath;
        listView.Rebuild();
    }

    private void Example_BindGroupRowElement_BindPath(VisualElement arg1, int arg2)
    {
        // This never gets called!
        Debug.Log($"Example_BindGroupRowElement_Auto {arg1}, {arg2}");
    }
}

I would prefer to use the bindingPath only but I cannot seem to get it to work. Is it possible to to so with nested ListView’s? If so what should the binding path be in my case?

If the manual binding/itemsSource is the only option then why are changes not being reflected? I have tried manually rebinding after any child items are added but it doesn’t seem to make any difference.

Any help is appreciated,
Thanks

Try setting an absolute bindingPath to m_Items in BindChildListElement_WithBindingPath. I think the path should be $"m_EnemyList.Array.data[{groupIndex}].m_Items".

You could also get the absolute path by getting the m_Items serializedProperty the same way you get it in BindChildListElement_ManualItemSource, then doing listView.bindingPath = children.propertyPath.

EDIT
Also, you should call Bind in your bindItem callbacks. In this case, listView.Bind(serializedObject).

Thanks fore the reply.

I had tried the full binding path previously without any luck. I noticed that since you can’t enter that type of path via the UI builder (complains about invalid characters) that it probably isn’t supported.

I tried again though with the Bind call and setting the full path call but throws an exception (ArgumentException: Can't find array size property!) on the Bind call.

Sorry, I didn’t see you did use the full binding path. The only thing I can think of now is that the path could be wrong. Maybe you could make sure by getting the path from SerializedProperty.propertPath and also checking that serialized property is an array.

im stuck on this exact same thing.
Trying to Make a list of list where List View A is the parent list, and List View B is a child list.

any information on how to properly achieve this would be appreciated.
Note that i am trying to pass down additional data to List B by from another source so that i can select some options within that particular list element

I never solved this the way I originally wanted as I could not get the binding to work. I suspect it was due to the fact that the child lists was not a list of Unity Object types but a list of custom class (with Serializable attribute).

I ended up ditching the nested list layout approach and just went for a 2 column layout where the right column would be a ListView that would re-bind based on the main list selection. It was annoying as I managed to build exactly what I wanted using the old editor GUI system :frowning:

Have you looked at the TreeView? It requires newer Unity version but it might be an option for you. Good luck!

Hi! Is there any way to use TreeView in an older version of Unity? Thanks!