ListView.ItemsRemoved called after removing

I have a ListView where I need to manage other assets based on this list changing.
When the item is removed from the list, the ItemsRemoved is called. But this is called after the item has been removed from itemsSource and the visual element has been removed. So the IEnumerable<int> thats being passed in on that event, seems to have no use?
You can’t access the visual element nor is the index valid in the itemsSource anymore.
I need to know what has been removed.

So how do I get the visual element or the index before it is actually being removed from the list?
I need to perform actions upon removal of an item in the list and for that I need a reference to the data that is bound to it. which means I need an event that happens just before the item has been removed. Not after.
Also would be nice if I could confirm it with a dialogue popup before actually removing it. Since it changes other assets based on this list.

I also just noticed that the documentation of ListView.itemsRemoved is wrong.

That should be “removed” not “added”

It would be nice to have some sort of diagram to know what is called in order.

I’ve submitted a feature request for ListView.itemRemoved (VisualElement, int) event on the Roadmap. Though if anyone knows a solution for this, that’d be nice.

TLDR; ListView item is removed from the list using the - button. You receive an itemsRemoved event but only after it has been removed from the list. Thus you never knowing what has been removed from the list.

Hi @MaskedMouse
You’re right that a callback just before could be useful. Thanks for submitting a feature request.

A way to do what you’re describing would be to inherit the ListViewController and override the RemoveItem/RemoveItems methods. You would be able to execute your code before calling the base implementation. The custom view controller can be assigned to the ListView with SetViewController.
Hope this helps!

2 Likes

I did not know you could do that. That should help quite a lot!
I’ll definitely give it a try.

I got in the same situation @MaskedMouse described, but I couldn’t inherit from ListViewController since it’s internal :frowning:

It has been made public in 22.1, so you might see it internal on 21.2 or not at all on older versions.

Yeah, indeed I was using 2021.2. Is it going to be added to 2021.3 LTS?

There is no plan for that at the moment unfortunately. The architecture of that part of the code changed quite a bit between the two versions, so to stay compatible, we would need to backport more changes than just switching internal to public.

I see 2 potential workarounds.
You could keep a dummy list on the side and keep it up to date manually with the add/removed callbacks from the ListView. That way you would have the data still available on remove.
This is by no mean the recommended solution and it is less than ideal, but you could probably build the class and assign it using reflection. That code will most likely be only compatible with 2021.2, so if you upgrade to 2022.1 at some point, you should go with the public API. But it would be a workaround in the meantime.

1 Like

I ended up doing just that and it works. As you said, it’s far from ideal, but it should be enough for now. I’ve created a dummy list and updated its contents via the add/removed callbacks from the ListView. This allows me to get a reference for the item that was just removed.

2 Likes

For anyone coming here in the future, I notice now in Unity 2022.3.7f1 that it calls the itemsRemoved event before removing items from the ListView (line 8):

// In BaseListViewController.cs
public virtual void RemoveItems(List<int> indices)
{
  this.EnsureItemSourceCanBeResized();
  if (indices == null)
    return;
  indices.Sort();
  // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  this.RaiseItemsRemoved((IEnumerable<int>) indices); <---- Event Called before removing.
  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  if (this.itemsSource.IsFixedSize)
  {
    this.itemsSource = (IList) BaseListViewController.RemoveFromArray((Array) this.itemsSource, indices);
  }
  else
  {
    for (int index = indices.Count - 1; index >= 0; --index)
      this.itemsSource.RemoveAt(indices[index]);
  }
  this.RaiseOnSizeChanged();
}

After testing it, it seems that it works fine now, itemsRemoved is triggered before the items at indices are removed. When was this changed?

1 Like

@DWO I believe this changed somewhere in 2022.1

1 Like