Structs as itemsSource?

Hi here!

Very simple code sample, I have a struct that contains some info for a listView’s items. I can add items from script and that works (see line 17).
But if add a new item through the ‘+’ footer icon (listview.showAddRemoveFooter = true) then I get an exception.
Now, exactly same code, I change the struct for a string (so items = new List) then the footer’s add works without errors.
The code:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
 
public struct stringBool
{
   public string text;
   public bool value;
}

public class SampleListView : MonoBehaviour
{
   public List<stringBool> items = new List<stringBool>();
   public void Start()
   {
       items.Add(new stringBool {text = "lalal"});
       var rootVisualElement = GetComponent<UIDocument>().rootVisualElement;
       var listView = rootVisualElement.Q<ListView>();
       var listItem = Resources.Load<VisualTreeAsset>("SampleListView");

       listView.itemsSource = items;
       listView.makeItem = () =>
       {
           return listItem.Instantiate();
       };
       listView.bindItem = (e, i) =>
       {
           e.Q<Label>().text = items[i].text;
           e.Q<Button>().text = "Button " + items[i].text;
           e.Q<RadioButton>().value = items[i].value;
       };
   }
}

…and the last lines of the error callstack:

ArgumentNullException: Value cannot be null.

Parameter name: item

System.Collections.Generic.List`1[T].System.Collections.IList.Add (System.Object item) (at <1fe4b731108247d3a7e57fad336611b9>:0)

UnityEngine.UIElements.ListViewController.AddItems (System.Int32 itemCount) (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/ListViewController.cs:66)

UnityEngine.UIElements.ListView.AddItems (System.Int32 itemCount) (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/Controls/ListView.cs:383)

UnityEngine.UIElements.ListView.OnAddClicked () (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/Controls/ListView.cs:447)

Help?

Some more info… if I use a class instead of a struct it works… better? I don’t get an internal error in Unity but I do get the listView.bindItem = (e, i) => call above being called with a items.count = 1 but a items[0] equals to (null). :expressionless:

So yeah… in case this helps anybody…

This works:

public class SampleListView : MonoBehaviour
{
   public class stringBool
   {
       public string text;
       public bool value;
   }

   List<stringBool> items = new List<stringBool>();
   public void Start()
   {
       var rootVisualElement = GetComponent<UIDocument>().rootVisualElement;
       var listView = rootVisualElement.Q<ListView>();
       var listItem = Resources.Load<VisualTreeAsset>("SampleListView");

       listView.itemsSource = items;
       listView.makeItem = () =>
       {
           return listItem.Instantiate();
       };
       listView.bindItem = (e, i) =>
       {
           items[i] = (items[i] is null)? new stringBool() : items[i];
           e.Q<Label>().text = items[i].text;              
           e.Q<Button>().text = "Button " + items[i].text;
           e.Q<RadioButton>().value = items[i].value;
       };
   }
}

A couple of things from there:

  • If the source is a list of structs it won’t work at all and it’ll throw an internal error (maybe constrain it to nullable types?)
  • I would imagine the entry created by the footer would be newed BEFORE the bind, but that’s not the case

But anyway, all good, forward we go!

1 Like