MVVM同步collections
有没有一种标准的方式来同步一组模型对象与在C#和WPF匹配的ModelView对象的集合? 我正在寻找一些类,将保持以下两个集合同步假设我只有几个苹果,我可以保留在记忆中。
另一种说法是,我想确定是否将Apple添加到Apple集合中。我想将AppleModelView添加到AppleModelViews集合中。 我可以通过听每个集合的CollectionChanged事件来写我自己的。 这似乎是一个比我更聪明的人已经定义了“正确的方式”来做到这一点的常见情况。
public class BasketModel { public ObservableCollection<Apple> Apples { get; } } public class BasketModelView { public ObservableCollection<AppleModelView> AppleModelViews { get; } }
我可能不完全理解您的需求,但是我处理类似情况的方式是在ObservableCollection上使用CollectionChanged事件,并根据需要简单地创build/销毁视图模型。
void OnApplesCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Only add/remove items if already populated. if (!IsPopulated) return; Apple apple; switch (e.Action) { case NotifyCollectionChangedAction.Add: apple = e.NewItems[0] as Apple; if (apple != null) AddViewModel(asset); break; case NotifyCollectionChangedAction.Remove: apple = e.OldItems[0] as Apple; if (apple != null) RemoveViewModel(apple); break; } }
在ListView中添加/删除大量项目时,可能会出现一些性能问题。
我们通过以下方法解决了这个问题:扩展ObservableCollection以使AddRange,RemoveRange,BinaryInsert方法和添加通知其他事件的集合正在被更改。 再加上一个扩展的CollectionViewSource,当集合发生改变时,临时断开源代码,它很好地工作。
HTH,
丹尼斯
我使用懒散构build的自动更新集合:
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; } } }
使用以下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; } } } }
那么首先,我不认为有一个“正确的方法”来做到这一点。 这完全取决于你的应用程序。 有更多正确的方法和不正确的方法。
这么多人说,我想知道为什么你需要保持这些集合“ 同步” 。 你考虑什么样的情况会使他们不同步? 如果你看一下Josh Smith 关于MV-VM的MSDN文章中的示例代码,你会发现大多数时候,模型与ViewModel保持同步,只是因为每次创buildModel时都会创build一个ViewModel 。 喜欢这个:
void CreateNewCustomer() { Customer newCustomer = Customer.CreateNewCustomer(); CustomerViewModel workspace = new CustomerViewModel(newCustomer, _customerRepository); this.Workspaces.Add(workspace); this.SetActiveWorkspace(workspace); }
我想知道,什么阻止你创build一个AppleModelView
每次创build一个Apple
? 除非我误解了你的问题,否则这似乎是保持这些集合“同步”的最简单的方法。
你也可以在这里find一个例子(和解释): http : //blog.lexique-du-net.com/index.php?post/2010/03/02/MV-VM-How-to-keep-collections-的-视图模型和-模型在同步
希望这个帮助
本文使用MVVM提供的撤消/重做提供了MirrorCollection类来实现视图模型和模型集合的同步。
http://blog.notifychanged.com/2009/01/30/viewmodelling-lists/
好吧,我有一个书呆子暗恋这个答案,所以我不得不分享这个抽象工厂,我补充到它支持我的注射器。
using System; using System.Collections.ObjectModel; namespace MVVM { public class ObservableVMCollectionFactory<TModel, TViewModel> : IVMCollectionFactory<TModel, TViewModel> where TModel : class where TViewModel : class { private readonly IVMFactory<TModel, TViewModel> _factory; public ObservableVMCollectionFactory( IVMFactory<TModel, TViewModel> factory ) { this._factory = factory.CheckForNull(); } public ObservableCollection<TViewModel> CreateVMCollectionFrom( ObservableCollection<TModel> models ) { Func<TModel, TViewModel> viewModelCreator = model => this._factory.CreateVMFrom(model); return new ObservableVMCollection<TViewModel, TModel>(models, viewModelCreator); } } }
build立在此之上的是:
using System.Collections.ObjectModel; namespace MVVM { public interface IVMCollectionFactory<TModel, TViewModel> where TModel : class where TViewModel : class { ObservableCollection<TViewModel> CreateVMCollectionFrom( ObservableCollection<TModel> models ); } }
和这个:
namespace MVVM { public interface IVMFactory<TModel, TViewModel> { TViewModel CreateVMFrom( TModel model ); } }
这里是空白检查完整性:
namespace System { public static class Exceptions { /// <summary> /// Checks for null. /// </summary> /// <param name="thing">The thing.</param> /// <param name="message">The message.</param> public static T CheckForNull<T>( this T thing, string message ) { if ( thing == null ) throw new NullReferenceException(message); return thing; } /// <summary> /// Checks for null. /// </summary> /// <param name="thing">The thing.</param> public static T CheckForNull<T>( this T thing ) { if ( thing == null ) throw new NullReferenceException(); return thing; } } }
我已经编写了一些帮助器类,用于在这里的View Model对象中封装可观察的业务对象集合
我真的很喜欢280Z28的解决scheme。 只有一句话。 是否有必要为每个NotifyCollectionChangedAction执行循环? 我知道行动的文档状态“一个或多个项目”,但由于ObservableCollection本身不支持添加或删除范围,这是不会发生,我会想。
将集合重置为默认值或匹配目标值是我经常碰到的东西
我写了一个小辅助类Miscilanious方法,其中包括
public static class Misc { public static void SyncCollection<TCol,TEnum>(ICollection<TCol> collection,IEnumerable<TEnum> source, Func<TCol,TEnum,bool> comparer, Func<TEnum, TCol> converter ) { var missing = collection.Where(c => !source.Any(s => comparer(c, s))).ToArray(); var added = source.Where(s => !collection.Any(c => comparer(c, s))).ToArray(); foreach (var item in missing) { collection.Remove(item); } foreach (var item in added) { collection.Add(converter(item)); } } public static void SyncCollection<T>(ICollection<T> collection, IEnumerable<T> source, EqualityComparer<T> comparer) { var missing = collection.Where(c=>!source.Any(s=>comparer.Equals(c,s))).ToArray(); var added = source.Where(s => !collection.Any(c => comparer.Equals(c, s))).ToArray(); foreach (var item in missing) { collection.Remove(item); } foreach (var item in added) { collection.Add(item); } } public static void SyncCollection<T>(ICollection<T> collection, IEnumerable<T> source) { SyncCollection(collection,source, EqualityComparer<T>.Default); } }
它涵盖了我的大部分需求,第一个可能是最适用于您的转换types
注意:这只能同步集合中的元素而不是它们中的值
Sam Harwell的解决scheme已经相当不错了,但有两个问题:
- 在这里注册的事件处理程序
this._source.CollectionChanged += OnSourceCollectionChanged
永远不会被注销,即一个this._source.CollectionChanged -= OnSourceCollectionChanged
丢失。 - 如果事件处理程序曾附加到由
viewModelFactory
生成的视图模型的viewModelFactory
,则无法知道这些事件处理程序何时可以再次分离。 (或者一般来说:你不能为生成的视图模型准备“销毁”。)
因此,我提出了一个解决scheme,可以解决萨姆·哈威尔(Sam Harwell)方法的两个缺点:
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics.Contracts; using System.Linq; namespace Helpers { public class ObservableViewModelCollection<TViewModel, TModel> : ObservableCollection<TViewModel> { private readonly Func<TModel, TViewModel> _viewModelFactory; private readonly Action<TViewModel> _viewModelRemoveHandler; private ObservableCollection<TModel> _source; public ObservableViewModelCollection(Func<TModel, TViewModel> viewModelFactory, Action<TViewModel> viewModelRemoveHandler = null) { Contract.Requires(viewModelFactory != null); _viewModelFactory = viewModelFactory; _viewModelRemoveHandler = viewModelRemoveHandler; } public ObservableCollection<TModel> Source { get { return _source; } set { if (_source == value) return; this.ClearWithHandling(); if (_source != null) _source.CollectionChanged -= OnSourceCollectionChanged; _source = value; if (_source != null) { foreach (var model in _source) { this.Add(CreateViewModel(model)); } _source.CollectionChanged += OnSourceCollectionChanged; } } } 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.RemoveAtWithHandling(e.OldStartingIndex); break; case NotifyCollectionChangedAction.Replace: // remove for (int i = 0; i < e.OldItems.Count; i++) this.RemoveAtWithHandling(e.OldStartingIndex); // add goto case NotifyCollectionChangedAction.Add; case NotifyCollectionChangedAction.Reset: this.ClearWithHandling(); if (e.NewItems == null) break; for (int i = 0; i < e.NewItems.Count; i++) this.Add(CreateViewModel((TModel)e.NewItems[i])); break; default: break; } } private void RemoveAtWithHandling(int index) { _viewModelRemoveHandler?.Invoke(this[index]); this.RemoveAt(index); } private void ClearWithHandling() { if (_viewModelRemoveHandler != null) { foreach (var item in this) { _viewModelRemoveHandler(item); } } this.Clear(); } private TViewModel CreateViewModel(TModel model) { return _viewModelFactory(model); } } }
为了处理这两个问题中的第一个问题,可以简单地将Source
设置为null以摆脱CollectionChanged
事件处理程序。
为了处理这两个问题中的第二个,你可以简单地添加一个viewModelRemoveHandler
,它允许“准备你的对象进行销毁”,例如删除所有附加的事件处理程序。