I’m looking at the sample for the ListView in the master sample project and I’m not entirely sure why it takes an a list of Items as its source, but also has a function to make items which doesn’t use any of the data from the list of Items used as a source. The two are linked through the bindItem function.
Couldn’t you do all of the stuff in the makeItem function outside of the ListView and then just use the completed as the itemsSource?
I believe makeItem is expected to be a function the returns a visual element of what you want each item in the list to be represented as. Bind is supposed to be a function to bind each of those template instances to data.
Could be missing the mark tho, I don’t have my code in front of me atm.
The window accessible from Window > UI > UIElements Samples in 19.2 and above has an improved ListView example:
// Create some list of data, here simply numbers in interval [1, 1000]
const int itemCount = 1000;
var items = new List<string>(itemCount);
for (int i = 1; i <= itemCount; i++)
items.Add(i.ToString());
// The "makeItem" function will be called as needed
// when the ListView needs more items to render
Func<VisualElement> makeItem = () => new Label();
// As the user scrolls through the list, the ListView object
// will recycle elements created by the "makeItem"
// and invoke the "bindItem" callback to associate
// the element with the matching data item (specified as an index in the list)
Action<VisualElement, int> bindItem = (e, i) => (e as Label).text = items[i];
var listView = container.Q<ListView>();
listView.makeItem = makeItem;
listView.bindItem = bindItem;
listView.itemsSource = items;
listView.selectionType = SelectionType.Multiple;
// Callback invoked when the user double clicks an item
listView.onItemChosen += obj => Debug.Log(obj);
// Callback invoked when the user changes the selection inside the ListView
listView.onSelectionChanged += objects => Debug.Log(objects);
Just recapping to see if I understand correctly, the makeItems function is used to construct the visual elements that make up each item in the list, with the itemsSource being the list of items in the entire list and the bindign function links the two. Is that right?
Are there any plans to move the makeItems part into the UXML like WPF does with its ListView’s ItemTemplates?
For me it is strange that they need to designate items source two times actually: at listView.itemsSource property and at listView.bindItem delegate. Perhaps it is better to use some additional parameter in bindItem delegate - something like
@StephanieRowlinson Yes! In 23.2 we introduced runtime bindings. Now it’s possible to have an item template assigned to the ListView and in this item template you can have bindings inside as WPF item templates. For this to work you need to make sure to set your itemsSource and set bindingSourceSelectionMode to AutoAssign.
Using this setup, there’s no need for assigning makeItem and bindItem anymore.
@cpalma-unity , this works. But can’t find anything about bindingSourceSelectionMode in the manual - is this feature too new for now?
Now the binding is almost entirely configurable through UI Builder, without using a code. Just for dynamic selection a source you need to set itemsSource in code. But in comparison with WPF, it is difficult to do so in UXML, because it looks like this for choosing, for example, a template
What is with all of these fileID, guid and so on? How can I write all of this in UXML manually? There is a lack of autocomplete in UI Builder and Visual Studio support for UXML. So, the only suitable way to do it is just to use Builder. Through code it is also too combersome. And in WPF you usually do it also in markup, not code. It is also suitable, but goes to more complexity when you need to debug bindings. As far as I remember, it took years from Microsoft to add a suitable support to check and debug bindings and so. In those days you usually needed a debug converter to check if some value actually comes or goes.
About the url in item-template, if you can ignore the fileID and so on part when using an IDE. I believe it’s used to keep a better reference for assets in the UI Builder in case they are deleted or moved.
In terms of debugging, we are aware of the need of having more debugging tools for bindings. This is something that will be added in the future.
Strange… Tried to implement the runtime binding as it described here (the last code sample at implementing both IDataSourceViewHashProvider and INotifyBindablePropertyChanged). I wrote a data source type InventoryItem with a string property DisplayName defined as in the article at the link. Then I set it through UI Builder like this for a Label visual element:
All works fine. Then set breakpoint at getter and setter of my data source object (InventoryItem in my case). Start a game - at the assigning of the DisplayName its setter is called once. But getter is called every frame. So, where is the computational economy with IDataSourceViewHashProvider and INotifyBindablePropertyChanged? If I remove the implementation of IDataSourceViewHashProvider and INotifyBindablePropertyChanged, and leave the DisplayName like this
string displayName;
[CreateProperty]
public string DisplayName
{
get => displayName;
set => displayName = value;
}
or even as an autoproperty, then I get the same result - only a single calling of the setter at assigning the binding source, and calling the getter every frame. I thought, the getter should not be called, until the Publish method is called.Unity 2023.2.3f1
So, this ListView bindings are like WPF, but not fully like WPF. You can assign an item template and bind all inside it, but items source of the list view itself is not bindable - you should assign it manually in code (not even in UI builder), as far as I understood.
The second thing, to update the items in the ListView, you need to manipulate the source collection for the ListView.itemssource, not the ListView.itemssource itself (ListView.itemssource = myCollection, so the myCollection should be manipulated). It is like in WPF, but… To mirror all the manipulations of the source collection to the ListView, the source collection is not required to be of type ObservableCollection or implement some type of notifications - it is enough the collection implements IList interface. It seems like ListView tracks all the changes of the source collection itself.
WPF bindings are more consistent, and in Unity they work like “here in this manner, there in other manner”. It confuses a little.
I very much agree with you, here is the current Listview sample code:
var items = new List<string>(itemCount);
//...
Action<VisualElement, int> bindItem = (e, i) => (e as Label).text = items[i];
listView.bindItem = bindItem;
listView.itemsSource = items;
binditem is not actually referencing itemsSource, they just ‘accidentally’ pointed to the same address.
This results in that if you want to switch the data source, you must modify the data on the source address. In the above situation, it would be like this:
items[i]="something changed"
Otherwise, if you just modify the itemsSource alone, the ListView will still use ‘items’ as the data source, but use the new itemsSource as the basis for the number of elements.
Of course, you can also modify itemsSource and bindItem at the same time. Just remember that itemsSource should be modified before bindItem, otherwise bindItem will trigger RefreshItems() at the wrong time.
All in all I think this is a design mistake. This is not how an easy-to-use framework should be designed