Is there a standardized way to sync a collection of Model objects with a collection of matching ModelView objects in C# and WPF? I'm looking for some kind of class that would keep the following two collections synced up assuming I only have a few apples and I can keep them all in memory.
Another way to say it, I want to make sure if I add an Apple to the Apples collection I would like to have an AppleModelView added to the AppleModelViews collection. I could write my own by listening to each collections' CollectionChanged event. This seems like a common scenario that someone smarter than me has defined "the right way" to do it.
public class BasketModel { public ObservableCollection<Apple> Apples { get; } } public class BasketModelView { public ObservableCollection<AppleModelView> AppleModelViews { get; } }
I use lazily constructed, auto-updating collections:
public class BasketModelView { private readonly Lazy<ObservableCollection<AppleModelView>> _appleViews; public BasketModelView(BasketModel basket) { Func<AppleModel, AppleModelView> viewModelCreator = model => new AppleModelView(model); Func<ObservableCollection<AppleModelView>> collectionCreator = () => new ObservableViewModelCollection<AppleModelView, AppleModel>(basket.Apples, viewModelCreator); _appleViews = new Lazy<ObservableCollection<AppleModelView>>(collectionCreator); } public ObservableCollection<AppleModelView> Apples { get { return _appleViews.Value; } } }
Using the following ObservableViewModelCollection<TViewModel, TModel>
:
namespace Client.UI { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics.Contracts; using System.Linq; public class ObservableViewModelCollection<TViewModel, TModel> : ObservableCollection<TViewModel> { private readonly ObservableCollection<TModel> _source; private readonly Func<TModel, TViewModel> _viewModelFactory; public ObservableViewModelCollection(ObservableCollection<TModel> source, Func<TModel, TViewModel> viewModelFactory) : base(source.Select(model => viewModelFactory(model))) { Contract.Requires(source != null); Contract.Requires(viewModelFactory != null); this._source = source; this._viewModelFactory = viewModelFactory; this._source.CollectionChanged += OnSourceCollectionChanged; } protected virtual TViewModel CreateViewModel(TModel model) { return _viewModelFactory(model); } private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: for (int i = 0; i < e.NewItems.Count; i++) { this.Insert(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i])); } break; case NotifyCollectionChangedAction.Move: if (e.OldItems.Count == 1) { this.Move(e.OldStartingIndex, e.NewStartingIndex); } else { List<TViewModel> items = this.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToList(); for (int i = 0; i < e.OldItems.Count; i++) this.RemoveAt(e.OldStartingIndex); for (int i = 0; i < items.Count; i++) this.Insert(e.NewStartingIndex + i, items[i]); } break; case NotifyCollectionChangedAction.Remove: for (int i = 0; i < e.OldItems.Count; i++) this.RemoveAt(e.OldStartingIndex); break; case NotifyCollectionChangedAction.Replace: // remove for (int i = 0; i < e.OldItems.Count; i++) this.RemoveAt(e.OldStartingIndex); // add goto case NotifyCollectionChangedAction.Add; case NotifyCollectionChangedAction.Reset: Clear(); for (int i = 0; i < e.NewItems.Count; i++) this.Add(CreateViewModel((TModel)e.NewItems[i])); break; default: break; } } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With