Runtime bindings with ListView (about INotifyBindablePropertyChanged)

About the new binding system in Unity 6 that you introduced. I’m interested in the part with INotifyBindablePropertyChanged, IDataSourceViewHashProvider, and ListView.

I have a strong feeling that something is fundamentally wrong here, and I suspect this is some kind of surreal mess.

You made it possible to implement list binding to ListView at the code level:

listView.SetBinding("itemsSource",
new DataBinding()
{
  dataSourcePath = new PropertyPath(nameof(PartyDataSource.members)),
  bindingMode = BindingMode.ToTarget,
});

And it works, yes, but it updates everything every time… I mean, right out of the box, if you go through all the basic setup steps, the ListView elements inside the ListView will update every frame (all elements).

Apparently, this happens because the code above directly assigns each element a data source – list, and a data source path – index. Which is kind of funny because this fundamentally prevents any optimization at the List level.

Since the initial entry point is List, I tried implementing my own class on top of List with INotifyBindablePropertyChanged, but that doesn’t really help much. And it seems like there is some code generation allowing this to work with List but not with CustomList:List.

So the question is – how exactly is this supposed to be implemented using built-in tools?

Hi @xacce

I have a strong feeling that something is fundamentally wrong here, and I suspect this is some kind of surreal mess.

Nothing fundamentally wrong happening. We are simply missing a few pieces to improve the binding performance of the collection views. For collections, INotifyBindablePropertyChanged is not enough to capture all the collection operations that we need to reduce the amount of binding updates. We are aware of this and we will add to it in time.

Given the release window we had at the time, we decided to release the runtime bindings system without full support for collections. It can still be used with the list views, but as you mentioned, the bindings will be updated every frame.

Using the list view setup where we will assign the dataSource and the dataSourcePath of list items, you could use IDataSourceViewHashProvider to limit the number of updates on the whole list, but for proper support, we will need to release a new interface for collection changes.

Which is kind of funny because this fundamentally prevents any optimization at the List level.

What do you mean by this? The ListView itself works with the itemsSource and the item index, so to have additional optimizations, you would need to have a custom list view.

Since the initial entry point is List, I tried implementing my own class on top of List with INotifyBindablePropertyChanged, but that doesn’t really help much. And it seems like there is some code generation allowing this to work with List but not with CustomList:List.

I’ll have to check, but I believe the code generation will still allow it, but it will use the List<T> or IList<T> version of it, which at the moment, will not allow to add additional properties on it. Alongside the new collection interface, we will most likely release a new list type that will handle the necessary operations.

Hope this helps!

Can you give a example how implementing the interface can help here, because I don’t see any difference.

My “MainDataSource” class, which contains the list and the “ItemDataSource” class which is used by the list, both fully implement INotifyBindablePropertyChanged and IDataSourceViewHashProvider and I still see the same behavior. Every single element of the list and every property getter is called every frame. The binding seems to totally ignore the interfaces in this context.

I tried to implement a custom list type which implements IList and IList T but as soon as I add any element to it i get the following exception:

I also had to add the [CreateProperty] attribute to the indexer property, otherwise it the bindings would shown an error about an invalid path [0].SomeProperty.
The binding error is gone after I added the attribute, but the exception is still the same.

ArgumentException: method arguments are incompatible
System.Delegate.CreateDelegate (System.Type type, System.Object firstArgument, System.Reflection.MethodInfo method, System.Boolean throwOnBindFailure, System.Boolean allowClosed) (at :0)
System.Delegate.CreateDelegate (System.Type type, System.Reflection.MethodInfo method, System.Boolean throwOnBindFailure) (at :0)
System.Delegate.CreateDelegate (System.Type type, System.Reflection.MethodInfo method) (at :0)
Unity.Properties.ReflectedMemberProperty2[TContainer,TValue]..ctor (Unity.Properties.IMemberInfo info, System.String name) (at <b9a4926d6fad4014a488c7d9d8821d70>:0) Unity.Properties.Internal.ReflectedPropertyBagProvider.CreateProperty[TContainer,TValue] (Unity.Properties.IMemberInfo member, Unity.Properties.Internal.ReflectedPropertyBag1[TContainer] propertyBag) (at :0)
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object parameters, System.Globalization.CultureInfo culture) (at :0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object parameters, System.Globalization.CultureInfo culture) (at :0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object parameters) (at :0)
Unity.Properties.Internal.ReflectedPropertyBagProvider.CreatePropertyBag[TContainer] () (at :0)
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object parameters, System.Globalization.CultureInfo culture) (at :0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object parameters, System.Globalization.CultureInfo culture) (at :0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object parameters) (at :0)
Unity.Properties.Internal.ReflectedPropertyBagProvider.CreatePropertyBag (System.Type type) (at :0)
Unity.Properties.Internal.PropertyBagStore.GetPropertyBag (System.Type type) (at :0)
Unity.Properties.PropertyContainer.TryAccept[TContainer] (Unity.Properties.IPropertyBagVisitor visitor, TContainer& container, Unity.Properties.VisitReturnCode& returnCode, Unity.Properties.VisitParameters parameters) (at :0)
UnityEngine.UIElements.BindingUpdater.VisitAtPath[TContainer] (UnityEngine.UIElements.DataBinding dataBinding, UnityEngine.UIElements.BindingUpdateStage direction, TContainer& container, Unity.Properties.PropertyPath& path, UnityEngine.UIElements.BindingContext& context) (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.BindingUpdater.UpdateUI (UnityEngine.UIElements.BindingContext& context, UnityEngine.UIElements.DataBinding dataBinding) (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.BindingUpdater.UpdateUI (UnityEngine.UIElements.BindingContext& context, UnityEngine.UIElements.Binding bindingObject) (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.VisualTreeDataBindingsUpdater.Update () (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.VisualTreeUpdater.UpdateVisualTreePhase (UnityEngine.UIElements.VisualTreeUpdatePhase phase) (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.Panel.UpdateDataBinding () (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.Panel.TickSchedulingUpdaters () (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.Panel.UpdateForRepaint () (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.Panel.Repaint (UnityEngine.Event e) (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.UIElementsRuntimeUtility.RepaintPanel (UnityEngine.UIElements.BaseRuntimePanel panel) (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.UIElementsRuntimeUtility.RepaintPanels (System.Boolean onlyOffscreen) (at <8966e118d0054112ab94138b090bd323>:0)
UnityEngine.UIElements.UIElementsRuntimeUtilityNative.RepaintPanels (System.Boolean onlyOffscreen) (at <8966e118d0054112ab94138b090bd323>:0)

HI @R1PFake,

Can you post your custom list implementation?

I already delete it, but it also happens if you simply derive from the existing List class, like this:

public class MyList<T> : List<T> {  }

The ListView binding doesn’t seem to like anything other than a direct List< T > at the moment.

Yeah, this is what I meant by this:

The current system will use the property bag for lists and won’t allow other properties.

I didn’t add any additional properties to MyList, I only created and used a instance of MyList (which is derived from List) instead of using List directly, because I wanted to see if the same error shows up as with my custom IList implementation and it does.

It doesn’t matter if it is IList < T> or anything derived from List < T> it must be a List < T > directly, anything else will throw the exception shown above. At least in my version, which is Unity 6.2.9