ReactiveUI 9: binding lists to a WPF view
Asked Answered
R

1

7

In ReactiveUI 9, ReactiveList has been deprecated in favor of DynamicData (Blog post). I am currently in the process of trying to update my code to use SourceList. I was able to get the ViewModel to work, however it seems that using SourceList as a binding datasource in WPF is not as easy.

My first attempt was to create the binding as was done in previous versions of ReactiveUI:

this.OneWayBind(ViewModel, vm => vm.MyList, v => v.MyListView.ItemsSource);

This doesn't work, because SourceList is not enumerable (Can't convert DynamicData.ISourceList to System.Collections.IEnumerable)

My second attempt was to use the Items property of the list.

this.OneWayBind(ViewModel, vm => vm.MyList.Items, v => v.MyListView.ItemsSource);

This doesn't work because the Items getter internally creates a copy of the list, which means changes in the list won't be reflected in the view.

My third attempt was to use the Bind method to create a ReadOnlyObservableCollection. I don't want to do this in the viewmodel, because then I would have to add a second list property for every list in every viewmodel which clutters up my code, violating the DRY principle. Furthermore, the type of list to bind to depends on the view framework that is used. (for example: WinForms uses BindingList instead)

Also, the viewmodel of my view might change, which means that the resulting binding and list must be cleaned up and replaced when a new viewmodel is set. This gives me the following snippet:

this.WhenAnyValue(v => v.ViewModel.VisibleInputs)
    .Select(l =>
    {
        var disposer = l.Connect().Bind(out var list).Subscribe();
        return (List: list, Disposer: disposer);
    })
    .PairWithPreviousValue()
    .Do(p => p.OldValue.Disposer?.Dispose()) // Cleanup the previous list binding
    .Select(p => p.NewValue.List)
    .BindTo(this, v => v.InputsList.ItemsSource);

This works fine, but its pretty verbose. I could create an extension method for this, but maybe there is a better/built-in way to do WPF list binding with DynamicData?

Rustyrut answered 10/12, 2018 at 14:51 Comment(0)
C
10

I think the idea is that the view binds to an IObservableCollection<T> on the dispatcher thread and that the SourceList<T> feeds this one with the objects that the stream produces, e.g.:

public class MainViewModel : ReactiveObject
{
    private SourceList<int> _myList { get; } = new SourceList<int>();
    private readonly IObservableCollection<int> _targetCollection = new ObservableCollectionExtended<int>();

    public MainViewModel()
    {
        Task.Run(()=> 
        {
            for (int i = 0; i < 100; ++i)
            {
                _myList.Add(i);
                System.Threading.Thread.Sleep(500);
            }
        });
        _myList.Connect()
            .ObserveOnDispatcher()
            .Bind(_targetCollection)
            .Subscribe();            
    }

    public IObservableCollection<int> TargetCollection => _targetCollection;
}

View:

this.OneWayBind(ViewModel, vm => vm.TargetCollection, v => v.MyListView.ItemsSource);

You can read more about this here.

Callisthenics answered 10/12, 2018 at 15:48 Comment(2)
if you aren't doing multithreaded related operations, nor doing the operations associated with the old ReactiveDerivedList, you can just use a ObservableCollectionExtended.Rathenau
Alright, I guess this answers my question. Kind of annoying that this now requires adding an extra list everywhere ReactiveList was used. That will definitely not make the code more readable. I guess I'll write an extension method to hide this boilerplate.Rustyrut

© 2022 - 2024 — McMap. All rights reserved.