I'm using the excellent dynamicdata for bridging my data layer to my UI (using RxUI). The dynamic data part boils down to the following typical piece of code. There is a SourceCache of ItemViewModels ("_sourceCache") which gets bound to a ReactiveList of ItemViewModels ("Models") which in turn (in the View) is bound as a ListView.ItemsSource:
var propertyChanges = _sourceCache.Connect().WhenPropertyChanged(p => p.LastChange)
.Throttle(TimeSpan.FromMilliseconds(250))
.Select(_ => Unit.Default);
var comparer = SortExpressionComparer<ItemViewModel>.Ascending(l => l.LastChange);
_sourceCache.Connect()
.Sort(comparer, propertyChanges)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(Models)
.Subscribe();
(a complete working example can be found here)
When running the example, the whole list reloads visible everytime the sort gets triggered by the Property Change (watching the LastChange property). If selecting a ListViewItem, often the selection gets lost, plus the visible reloading of the whole list is rather distracting.
My question now is: Is there any way I can sort in my DynamicData chain and not have the mentioned problems?
Ideally only the changed items will change their position in the list and the rest of the items would stay visible.
Update
Thanks Roland for the hint, but the observed behavior is still the same, regardless of using
Sort(comparer, propertyChanges)
or
AutoRefresh(m => m.LastChange)
.Sort(comparer)
The whole List UI still looks like it reloads (all items disappear and reappear on every change). As can be seen in my linked github repo, I randomly choose one of the bound ItemViewModels and change the "LastChanged" property. Maybe it is a behavior of the UWP ListView?
Update 2
The same behaviour can be seen if I just ReactiveList.Move randomly one item every 1sec, still the whole list looks as if it is reloaded every time. So I guess it is the UWP ListView itself...
Update
Implemented a new Operation for DynamicData that converts "move" changes into an "remove on old index" and "add on new index" called TreatMovesAsRemoveAdd, to be used like this:
_sourceCache.Connect()
.Sort(comparer, propertyChanges)
.TreatMovesAsRemoveAdd()
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(Models)
.Subscribe();
old answer:
It seems the ListView cannot handle (semantically) moving items around. just add+delete. So I followed Roland's advice and modified the SortedReactiveListAdaptor (copied it and modified the following method:
private void DoUpdate(IChangeSet<TObject, TKey> changes)
{
foreach (var change in changes)
{
switch (change.Reason)
{
case ChangeReason.Add:
_target.Insert(change.CurrentIndex, change.Current);
break;
case ChangeReason.Remove:
_target.RemoveAt(change.CurrentIndex);
break;
case ChangeReason.Moved:
// ************************************** change from original ************************************************
//_target.Move(change.PreviousIndex, change.CurrentIndex);
//break;
// ************************************** ******************** ************************************************
case ChangeReason.Update:
{
_target.RemoveAt(change.PreviousIndex);
_target.Insert(change.CurrentIndex, change.Current);
}
break;
}
}
}
So all moves will be treated as updates (RemoteAt + Insert) Application of said SortedReactiveListAdaptor would be:
.Bind(new MovesToUpdatesReactiveListAdaptor<IItemViewModel, string>(Items))
instead of the usual
.Bind(Items)