I have two models of the form

public class Model1 { public Int32 SomeProperty1 { get; set; } public event Action<Int32> SomeProperty1Changed; } public class Model2 { public Int32 SomeProperty1 { get; set; } public event Action<Int32> SomeProperty1Changed; public ObservableCollection<Model1> Models { get; private set; } } 

These models can be edited in the background stream. This data from me should receive my Control that would draw them on Canvas through code behind.
With event handling simple properties, everything is clear. I create a ViewModel that will call Dispatcher.InvokeAsync() inside the event handler for synchronization.
But how can I deal with collection event handling?
Either the ViewModel acts as a wrapper over the model object, without doing anything with it. In this case, I need to call Dispatcher.InvokeAsync() in the code behind my Control for synchronization. In this case, the least unnecessary actions come out, but for some reason I don’t like this system. I understand this is a violation of MVVM, so I do not like it.
Either the ViewModel catches collection events and does the same with its collection, but already in the window stream. I like this option more, but here comes out more unnecessary movements.

    1 answer 1

    The correct way is that the VM should hide the details of the model from View, so the View code should in principle not suspect the existence of several threads.

    If your VM works with a collection of models and manages a collection of nested VMs, then yes, it needs to be engaged in transferring update events to the main thread.

    If you see that you get a lot of unnecessary, perhaps you should write once a helper method or class that will do this.

     class ObservableCollectionForwarder<S, D> : IDisposable { Func<S, D> mapper; ObservableCollection<S> source; ObservableCollection<D> destination; Dispatcher dispatcher; public ObservableCollectionForwarder<S, D>( ObservableCollection<S> source, ObservableCollection<D> destination, Func<S, D> mapper) { this.source = source; this.destination = destination; this.mapper = mapper; dispatcher = Dispatcher.CurrentDispatcher; source.NotifyCollectionChanged += OnSourceChanged; } public void Dispose() { source.NotifyCollectionChanged -= OnSourceChanged; } void OnSourceChanged(object o, NotifyCollectionChangedEventArgs args) { dispatcher.BeginInvoke((Action)() => OnSourceChangedInternal(o, args)); } void OnSourceChangedInternal(object o, NotifyCollectionChangedEventArgs args) { switch (args.Action) { case NotifyCollectionChangedAction.Add: { var index = args.NewStartingIndex; foreach (var targetItem in args.Cast<S>.Select(mapper)) target.Insert(index++, targetItem); break; } } ... } } 

    On the other hand, if you think that your model should communicate with the outside world in a different way (for example, set IObservable ), this will not change anything in the UI, only the binding will change.

     class RxForwarder<S, D> : IDisposable { Func<S, D> mapper; IObservable<S> source; ObservableCollection<D> destination; IDisposable subscription; public ObservableCollectionForwarder<S, D>( IObservable<S> source, ObservableCollection<D> destination, Func<S, D> mapper) { this.source = source; this.destination = destination; this.mapper = mapper; this.subscription = source .ObserveOn(Dispatcher.CurrentDispatcher) .Subscribe(OnNewItem); } public void Dispose() { subscription.Dispose; } void OnNewItem(S item) { target.Add(item); } }