Avalonia: Performance issues with SelectingItemsControl.SelectedItems

Created on 19 Jan 2020  路  4Comments  路  Source: AvaloniaUI/Avalonia

Lately I've been investigating a few performance related issues found around big virtualized lists and trees.

Setup was always quite similar and innocent:

  • we have a decently sized list (let's start with 100k elements)
  • we bind SelectedItems to an ObservableCollection<T> (or similar)
  • we subscribe to CollectionChanged event on said collection and then do some operation on the whole selection (let's say we calculate a sum of selected numbers)
  • user can potentially select all items in the collection

When recreating such setup in ControlCatalog (with a list of 100k integers and CollectionChanged handler summing SelectedItems) we are getting around 40 seconds freeze due to 100k discrete SelectionChanged events being raised.

We can't even batch such changes since we don't know which SelectionChanged event is last. And Avalonia cannot use AddRange method since there is no standard one available (https://github.com/dotnet/corefx/issues/10752, which actually got merged for a moment but was promptly reverted since it broke WPF).

This makes one wonder about usability of SelectedItems when dealing with bigger data and/or caring about performance in general (reducing UI stutters, etc.). Since even having a small collection and expensive calculation can freeze the whole application.

One can use Rx.Net buffering or throttling capabilities but this again increases complexity of the solution and makes debugging harder. And requires user to actually use reactive extensions and such basic concept as selection should work fine out of box in my opinion.

One is actually forced to subscribe to SelectionChanged event in this case (which is also broken, but that one is described in https://github.com/AvaloniaUI/Avalonia/pull/3435 and can be fixed).
I wonder about applicability in MVVM scenarios where developer may choose SelectedItems during development not realizing its limitations and then hitting a wall when bigger data set is used by the user, with no means of resolving this problem apart from a rewrite using SelectionChanged event.

enhancement

Most helpful comment

Yeah, this is definitely a problem, trying to cast to IAvaloniaList<object> isn't ideal either because IAvaloniaList isn't covariant.

There are actually a few problems here:

  • No AddRange on IList or similar interfaces meaning that we can't batch selections
  • Performing a "select all" on a large (virtualized) collection will cause every item in the collection to be added to SelectedItems - this could be a huge number of items
  • Storing the selected _items_ instead of indexes means that we can't properly handle multiple selections on collections with duplicate items

I think the solution (which I'm currently working on) is to port WinUI's SelectionModel to Avalonia. SelectionModel has the following properties:

  • Implements selection logic outside controls, so can be reused across controls without a common base class (ListBox and TreeView have separate selection implementations currently)
  • Selects controls by index ranges, so a "select all" on a collection of 100,000 items would simply select the range 0-99999
  • Supports selecting ranges from the API so notifications aren't raised for every item

My current thinking is that there would be a bindable direct property on e.g. SelectingItemsControl:

public ISelectionModel Selection { get; set; }

If unbound, the control would create its own SelectionModel or the user could bind it to a SelectionModel in their view model.

All 4 comments

From my understanding WPF can't deal with multiple added / removed items in one event but that isn't limiting us from supporting this.

Note that AvaloniaList does have AddRange available. So we could attempt to cast the collection to IAvaloniaList and do a batched change.

Yeah, this is definitely a problem, trying to cast to IAvaloniaList<object> isn't ideal either because IAvaloniaList isn't covariant.

There are actually a few problems here:

  • No AddRange on IList or similar interfaces meaning that we can't batch selections
  • Performing a "select all" on a large (virtualized) collection will cause every item in the collection to be added to SelectedItems - this could be a huge number of items
  • Storing the selected _items_ instead of indexes means that we can't properly handle multiple selections on collections with duplicate items

I think the solution (which I'm currently working on) is to port WinUI's SelectionModel to Avalonia. SelectionModel has the following properties:

  • Implements selection logic outside controls, so can be reused across controls without a common base class (ListBox and TreeView have separate selection implementations currently)
  • Selects controls by index ranges, so a "select all" on a collection of 100,000 items would simply select the range 0-99999
  • Supports selecting ranges from the API so notifications aren't raised for every item

My current thinking is that there would be a bindable direct property on e.g. SelectingItemsControl:

public ISelectionModel Selection { get; set; }

If unbound, the control would create its own SelectionModel or the user could bind it to a SelectionModel in their view model.

Ported SelectionModel but hit a few roadblocks:

  • SelectionModel.SelectionChanged doesn't actually tell you what changed (https://github.com/microsoft/microsoft-ui-xaml/issues/1645). This is a showstopper.
  • SelectionModel doesn't handle Reset events: it _can't_ because it only stores the selected indexes and has no idea how these indexes apply to the data after a reset. Not sure what to do about this.
  • It doesn't handle move or replace either (not sure this is an issue?)
  • It needs an AlwaysSelected flag adding (this one's easy)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

JonathaN7Shepard picture JonathaN7Shepard  路  4Comments

ZZerker picture ZZerker  路  4Comments

RUSshy picture RUSshy  路  4Comments

MarchingCube picture MarchingCube  路  4Comments

maxkatz6 picture maxkatz6  路  3Comments