Search Unity

UIElements ListView with serializedProperty of an array

Discussion in 'UIElements' started by Sangemdoko, Jul 31, 2019.

  1. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    51
    Hi,

    I am having problems with displaying a ListView from a serializedProperty of an array of a custom class.

    The reason I want this is to make a customPropertyDrawer for my property.
    The main limitation is that ListView requires an IList "itemSource".

    I tried getting the array value using Reflection (FieldInfo.GetValue) but the array I get is always of size 0, even though the property.arraySize is not.

    I assumed that what is happening is that since I am using a customClass and the elements were null and that the get value function would remove any null elements in the array.

    But I'm not sure that's the case because I tried FieldInfo.SetValue and that did not increase the serializedProperty array size.

    Has anyone managed to get something similar to work? How did you achieve it?

    I'll continue to try and get my propertyDrawer to work and I'll post my solution if I can get it to work
     
  2. imaewyn

    imaewyn

    Joined:
    Apr 23, 2016
    Posts:
    159
    Code (CSharp):
    1. [CustomEditor(typeof(List_SO))]
    2. public class SO_UI : Editor
    3. {    
    4.    SerializedProperty _listProperty;
    5.  
    6.     public override VisualElement CreateInspectorGUI()
    7.     {
    8.         serializedObject.Update();
    9.         _listProperty = serializedObject.FindProperty("firstLists");
    10.  
    11.         var root = new VisualElement();
    12.         root.Add(new Button(() => Test()));
    13.         for (int i = 0; i < _listProperty.arraySize; i++)
    14.         {
    15.             SerializedProperty property = _listProperty.GetArrayElementAtIndex(i);
    16.             root.Add(new PropertyField(property));
    17.         }
    18.         serializedObject.ApplyModifiedProperties();
    19.         return root;
    20.     }
    21.     public void Test()
    22.     {
    23.         Debug.Log("new void test");
    24.         _listProperty.arraySize += 1;
    25.     }
    26. }

    try to use something it
     
  3. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    43
    Proper ListView binding will be released in 19.3. I the meantime, I updated @imaewyn example code to use both a list view and a propertyfield to edit an array.

    Assuming you have this List_SO MonoBehaviour:

    public class List_SO : MonoBehaviour
    {
    public List<string> firstLists = new List<string>();
    }


    Here is the modified editor code. Keep in mind this is a workaround until you can call ListView.Bind() directly in 19.3 as this is less efficient:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.UIElements;
    4. using UnityEditor.UIElements;
    5. using System.Collections;
    6. using System;
    7. using System.Collections.Generic;
    8.  
    9. [CustomEditor(typeof(List_SO))]
    10. public class SO_UI : Editor
    11. {
    12.     SerializedProperty _listProperty;
    13.  
    14.     public override VisualElement CreateInspectorGUI()
    15.     {
    16.         _listProperty = serializedObject.FindProperty("firstLists");
    17.  
    18.         var root = new VisualElement();
    19.         var b = new Button(() => Test());
    20.         b.text = "Add";
    21.  
    22.         root.Add(b);
    23.         root.Add(new PropertyField(_listProperty));
    24.  
    25.         ListView lv = new ListView();
    26.         root.Add(lv);
    27.         lv.itemHeight = 20;
    28.         new ListViewBindingWhileWaitingForRealBindings(lv, _listProperty, true);
    29.  
    30.         //TODO: provide these styles in uss instead
    31.         lv.style.flexGrow = 1;
    32.         lv.style.minHeight = 200;
    33.  
    34.         return root;
    35.     }
    36.     public void Test()
    37.     {
    38.         _listProperty.arraySize += 1;
    39.         _listProperty.serializedObject.ApplyModifiedProperties();
    40.     }
    41. }
    42.  
    43.  
    44. // Somewhat inefficient workaround while waiting for real ListView binding support in 19.3
    45. class ListViewBindingWhileWaitingForRealBindings
    46. {
    47.     SerializedObjectList m_DataList;
    48.  
    49.     SerializedObject boundObject;
    50.     SerializedProperty boundProperty;
    51.     SerializedProperty m_ArraySize;
    52.     int m_ListViewArraySize;
    53.     bool m_DisplayArraySize;
    54.  
    55.     ListView listView { get; set;}
    56.  
    57.     public ListViewBindingWhileWaitingForRealBindings(ListView listView, SerializedProperty prop, bool displayArraySize)
    58.     {
    59.         boundProperty = prop;
    60.         boundObject = prop.serializedObject;
    61.  
    62.         m_DataList = new SerializedObjectList(prop, displayArraySize);
    63.         m_ArraySize = m_DataList.ArraySize;
    64.         m_ListViewArraySize = m_DataList.ArraySize.intValue;
    65.         m_DisplayArraySize = displayArraySize;
    66.  
    67.         this.listView = listView;
    68.  
    69.         if (listView.makeItem == null)
    70.         {
    71.             listView.makeItem = () => MakeListViewItem();
    72.         }
    73.  
    74.         if (listView.bindItem == null)
    75.         {
    76.             listView.bindItem = (v, i) => BindListViewItem(v, i);
    77.         }
    78.  
    79.         listView.itemsSource = m_DataList;
    80.  
    81.         listView.schedule.Execute( () => Update()).Every(100); //we poll a few times a second
    82.     }
    83.  
    84.     VisualElement MakeListViewItem()
    85.     {
    86.         return new PropertyField();
    87.     }
    88.  
    89.     void BindListViewItem(VisualElement ve, int index)
    90.     {
    91.         var field = ve as IBindable;
    92.         if (field == null)
    93.         {
    94.             //we find the first Bindable
    95.             field = ve.Query().Where(x => x is IBindable).First() as IBindable;
    96.         }
    97.  
    98.         if (field == null)
    99.         {
    100.             //can't default bind to anything!
    101.             throw new InvalidOperationException("Can't find BindableElement: please provide BindableVisualElements or provide your own Listview.bindItem callback");
    102.         }
    103.  
    104.         object item = listView.itemsSource[index];
    105.         var itemProp = item as SerializedProperty;
    106.         field.bindingPath = itemProp.propertyPath;
    107.         ve.Bind(itemProp.serializedObject);
    108.     }
    109.  
    110.     void UpdateArraySize()
    111.     {
    112.         m_DataList.RefreshProperties(boundProperty, m_DisplayArraySize);
    113.         m_ArraySize = m_DataList.ArraySize;
    114.         m_ListViewArraySize = m_ArraySize.intValue;
    115.         listView.Refresh();
    116.     }
    117.  
    118.  
    119.     public void Update()
    120.     {
    121.         try
    122.         {
    123.             if (boundObject != null && boundProperty != null)
    124.             {
    125.                 boundObject.UpdateIfRequiredOrScript();
    126.  
    127.                 int currentArraySize = m_ArraySize.intValue;
    128.  
    129.                 if (currentArraySize != m_ListViewArraySize)
    130.                 {
    131.                     UpdateArraySize();
    132.                 }
    133.             }
    134.         }
    135.         catch (ArgumentNullException)
    136.         {
    137.             //this can happen when serializedObject has been disposed of
    138.         }
    139.     }
    140. }
    141.  
    142. internal class SerializedObjectList : IList
    143. {
    144.     public SerializedProperty ArraySize { get; private set; }
    145.  
    146.     List<SerializedProperty> properties;
    147.     public SerializedObjectList(SerializedProperty parentProperty, bool includeArraySize)
    148.     {
    149.         RefreshProperties(parentProperty, includeArraySize);
    150.     }
    151.  
    152.     public void RefreshProperties(SerializedProperty parentProperty, bool includeArraySize)
    153.     {
    154.         var property = parentProperty.Copy();
    155.         var endProperty = property.GetEndProperty();
    156.  
    157.         property.NextVisible(true); // Expand the first child.
    158.  
    159.         properties = new List<SerializedProperty>();
    160.         do
    161.         {
    162.             if (SerializedProperty.EqualContents(property, endProperty))
    163.                 break;
    164.  
    165.             if (property.propertyType == SerializedPropertyType.ArraySize)
    166.             {
    167.                 ArraySize = property.Copy();
    168.                 if (includeArraySize)
    169.                 {
    170.                     properties.Add(ArraySize);
    171.                 }
    172.             }
    173.             else
    174.             {
    175.                 properties.Add(property.Copy());
    176.             }
    177.         }
    178.         while (property.NextVisible(false)); // Never expand children.
    179.  
    180.         if (ArraySize == null)
    181.         {
    182.             throw new ArgumentException("Can't find array size property!");
    183.         }
    184.     }
    185.  
    186.     public object this[int index]
    187.     {
    188.         get
    189.         {
    190.             return properties[index];
    191.         }
    192.         set
    193.         {
    194.             throw new NotImplementedException();
    195.         }
    196.     }
    197.  
    198.     public bool IsReadOnly => true;
    199.  
    200.     public bool IsFixedSize => true;
    201.  
    202.     public int Count => properties.Count;
    203.  
    204.     bool ICollection.IsSynchronized
    205.     {
    206.         get { return (properties as ICollection).IsSynchronized; }
    207.     }
    208.  
    209.     object ICollection.SyncRoot
    210.     {
    211.         get { return (properties as ICollection).SyncRoot; }
    212.     }
    213.  
    214.     public int Add(object value)
    215.     {
    216.         throw new NotImplementedException();
    217.     }
    218.  
    219.     public void Clear()
    220.     {
    221.         throw new NotImplementedException();
    222.     }
    223.  
    224.     public bool Contains(object value)
    225.     {
    226.         return IndexOf(value) >= 0;
    227.     }
    228.  
    229.     public void CopyTo(Array array, int index)
    230.     {
    231.         throw new NotImplementedException();
    232.     }
    233.  
    234.     public IEnumerator GetEnumerator()
    235.     {
    236.         return properties.GetEnumerator();
    237.     }
    238.  
    239.     public int IndexOf(object value)
    240.     {
    241.         var prop = value as SerializedProperty;
    242.  
    243.         if (value != null && prop != null)
    244.         {
    245.             return properties.IndexOf(prop);
    246.         }
    247.         return -1;
    248.     }
    249.  
    250.     public void Insert(int index, object value)
    251.     {
    252.         throw new NotImplementedException();
    253.     }
    254.  
    255.     public void Remove(object value)
    256.     {
    257.         throw new NotImplementedException();
    258.     }
    259.  
    260.     public void RemoveAt(int index)
    261.     {
    262.         throw new NotImplementedException();
    263.     }
    264. }
    265.  
     
    imaewyn likes this.
  4. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    51
    Thank you @UnityMat.
    The ListView.Bind will be available in 19.3. Will it be retrofitted to 2019.1 and 2019.2 too? or will it be only 2019.3 onwards?
     
  5. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    43
    It most likely won't be backported to 19.2. In the meantime, you can use this workaround, Updating it when 19.3 is released should be straighforward.
     
  6. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    51
    Knowing that will help me plan things in anticipation. Thank you for the help!