I'm using ReactiveUI and DynamicData in my C# project. However, domain model classes still rely on C# events, INotifyPropertyChanged and INotifyCollectionChanged interfaces.
There are Model and ViewModel classes:
public class Model
{
public ObservableCollection<int> Collection { get; } = new ObservableCollection<int>();
}
public class ViewModel : ReactiveObject, IDisposable
{
private readonly CompositeDisposable _cleanUp;
private readonly SourceList<int> _collectionForCurrentBModel = new SourceList<int>();
private Model _model = new Model();
private IDisposable _tempCleanUp = Disposable.Empty;
public ViewModel()
{
_cleanUp = new CompositeDisposable();
_collectionForCurrentBModel.Connect()
.Bind(out var aModelsForCurrentBModel)
.Subscribe(Console.WriteLine)
.DisposeWith(_cleanUp);
CollectionForCurrentBModel = aModelsForCurrentBModel;
this.WhenAnyValue(x => x.Model.Collection) // Every time Model in ViewModel changes:
.Subscribe(collection =>
{
// we dispose previous subscription:
_tempCleanUp.Dispose();
// then we manually reset SourceList<int> to match new collection:
_collectionForCurrentBModel.Edit(x =>
{
x.Clear();
x.AddRange(collection);
});
// finally, we manually subscribe to ObservableCollection<int>'s events to synchronize SourceList<int>.
_tempCleanUp = collection.ObserveCollectionChanges().Subscribe(pattern =>
{
switch (pattern.EventArgs.Action)
{
case NotifyCollectionChangedAction.Add:
_collectionForCurrentBModel.AddRange(pattern.EventArgs.NewItems.Cast<int>());
break;
case NotifyCollectionChangedAction.Remove:
_collectionForCurrentBModel.RemoveRange(pattern.EventArgs.OldStartingIndex,
pattern.EventArgs.OldItems.Count);
break;
case NotifyCollectionChangedAction.Replace:
for (var i = 0; i < pattern.EventArgs.NewItems.Count; i++)
_collectionForCurrentBModel.Replace((int) pattern.EventArgs.OldItems[i],
(int) pattern.EventArgs.NewItems[i]);
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new ArgumentOutOfRangeException();
}
});
});
}
public ReadOnlyObservableCollection<int> CollectionForCurrentBModel { get; }
public Model Model
{
get => _model;
set => this.RaiseAndSetIfChanged(ref _model, value);
}
public void Dispose()
{
_cleanUp.Dispose();
}
}
So, ViewModel has Model property. Current Model can be changed to another one. ViewModel also has CollectionForCurrentModel property, which is basically equal to its' source (Model.Collection) in this example (however, there is supposed to be some sorting, filtering, etc.). The CollectionForCurrentModel property should be read-only. The code below works as intended:
private static void Main(string[] args)
{
using var viewModel = new ViewModel();
// viewModel.Collection: {}
viewModel.Model.Collection.Add(0);
// viewModel.Collection: {0}
viewModel.Model.Collection.Add(1);
// viewModel.Collection: {0, 1}
var oldModel = viewModel.Model;
viewModel.Model = new Model();
// viewModel.Collection: {}
viewModel.Model.Collection.Add(2);
// viewModel.Collection: {2}
oldModel.Collection.Add(3);
// viewModel.Collection: {2}
}
However, adding new field to ViewModel for storing latest subscription, manually unsubscribing from it and manually synchronizing collections seems quite ugly. Is there another way to subscribe to:
IObservable<IObservable<IChangeSet<T>>>
\\ is result of
this.WhenAnyValue(x => x.ObservableCollection, selector: collection => collection.ToObservableChangeSet();
? Can DynamicData automatically manage inner subscriptions to bind the observable collection in mutable property to other collection?