Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactiveUI 9: binding lists to a WPF view

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?

like image 518
Wouter Avatar asked Mar 05 '23 22:03

Wouter


1 Answers

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.

like image 90
mm8 Avatar answered Mar 17 '23 05:03

mm8