MVVM和VM的集合
一个常见的senario:一个包含一系列项目模型的模型。
比如有一群人的房子。
如何正确地为MVVM构造 – 特别是关于使用添加和删除来更新Model和ViewModel集合?
Model House
包含模型People
(通常是List<People>
)的集合。
查看模型HouseVM
包含它封装的House对象和视图模型PeopleVM
( ObservableCollection<PeopleVM>
)的ObservableCollection<PeopleVM>
。 请注意,我们最后在HouseVM中持有两个集合(需要同步):
1. HouseVM.House.List<People>
2. HouseVM.ObservableCollection<PeopleVM>
当House更新为新的People(add)或People leave(remove)时,现在必须在两个集合中处理Model House People集合和 VM HouseVM PeopleVM ObservableCollection。
这个结构是否正确的MVVM?
是否有避免必须执行添加和删除的双重更新?
你的一般方法是非常好的MVVM,有一个ViewModel暴露其他ViewModel的集合是一个非常常见的情况,我用在所有的地方。 我不会直接在ViewModel中公开项目,就像nicodemus13说的那样,当你的视图绑定到没有ViewModel的模型之间时,你的collections项目就会结束。 所以,你的第一个问题的答案是:是的,这是有效的MVVM。
您在第二个问题中解决的问题是房屋模型中的人物模型列表与您的房屋ViewModel中的人物列表ViewModel之间的同步。 您必须手动执行此操作。 所以,没有办法避免这种情况。
你可以做什么:实现一个自定义的ObservableCollection<T>
, ViewModelCollection<T>
,它把它的变化推到一个底层的集合。 要获得双向同步,请将模型集合设置为ObservableCollection <>,并注册到ViewModelCollection中的CollectionChanged
事件。
这是我的实现。 它使用ViewModelFactory服务等等,但只是看看一般的主体。 我希望这有助于…
/// <summary> /// Observable collection of ViewModels that pushes changes to a related collection of models /// </summary> /// <typeparam name="TViewModel">Type of ViewModels in collection</typeparam> /// <typeparam name="TModel">Type of models in underlying collection</typeparam> public class VmCollection<TViewModel, TModel> : ObservableCollection<TViewModel> where TViewModel : class, IViewModel where TModel : class { private readonly object _context; private readonly ICollection<TModel> _models; private bool _synchDisabled; private readonly IViewModelProvider _viewModelProvider; /// <summary> /// Constructor /// </summary> /// <param name="models">List of models to synch with</param> /// <param name="viewModelProvider"></param> /// <param name="context"></param> /// <param name="autoFetch"> /// Determines whether the collection of ViewModels should be /// fetched from the model collection on construction /// </param> public VmCollection(ICollection<TModel> models, IViewModelProvider viewModelProvider, object context = null, bool autoFetch = true) { _models = models; _context = context; _viewModelProvider = viewModelProvider; // Register change handling for synchronization // from ViewModels to Models CollectionChanged += ViewModelCollectionChanged; // If model collection is observable register change // handling for synchronization from Models to ViewModels if (models is ObservableCollection<TModel>) { var observableModels = models as ObservableCollection<TModel>; observableModels.CollectionChanged += ModelCollectionChanged; } // Fecth ViewModels if (autoFetch) FetchFromModels(); } /// <summary> /// CollectionChanged event of the ViewModelCollection /// </summary> public override sealed event NotifyCollectionChangedEventHandler CollectionChanged { add { base.CollectionChanged += value; } remove { base.CollectionChanged -= value; } } /// <summary> /// Load VM collection from model collection /// </summary> public void FetchFromModels() { // Deactivate change pushing _synchDisabled = true; // Clear collection Clear(); // Create and add new VM for each model foreach (var model in _models) AddForModel(model); // Reactivate change pushing _synchDisabled = false; } private void ViewModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Return if synchronization is internally disabled if (_synchDisabled) return; // Disable synchronization _synchDisabled = true; switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var m in e.NewItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>()) _models.Add(m); break; case NotifyCollectionChangedAction.Remove: foreach (var m in e.OldItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>()) _models.Remove(m); break; case NotifyCollectionChangedAction.Reset: _models.Clear(); foreach (var m in e.NewItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>()) _models.Add(m); break; } //Enable synchronization _synchDisabled = false; } private void ModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (_synchDisabled) return; _synchDisabled = true; switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var m in e.NewItems.OfType<TModel>()) this.AddIfNotNull(CreateViewModel(m)); break; case NotifyCollectionChangedAction.Remove: foreach (var m in e.OldItems.OfType<TModel>()) this.RemoveIfContains(GetViewModelOfModel(m)); break; case NotifyCollectionChangedAction.Reset: Clear(); FetchFromModels(); break; } _synchDisabled = false; } private TViewModel CreateViewModel(TModel model) { return _viewModelProvider.GetFor<TViewModel>(model, _context); } private TViewModel GetViewModelOfModel(TModel model) { return Items.OfType<IViewModel<TModel>>().FirstOrDefault(v => v.IsViewModelOf(model)) as TViewModel; } /// <summary> /// Adds a new ViewModel for the specified Model instance /// </summary> /// <param name="model">Model to create ViewModel for</param> public void AddForModel(TModel model) { Add(CreateViewModel(model)); } /// <summary> /// Adds a new ViewModel with a new model instance of the specified type, /// which is the ModelType or derived from the Model type /// </summary> /// <typeparam name="TSpecificModel">Type of Model to add ViewModel for</typeparam> public void AddNew<TSpecificModel>() where TSpecificModel : TModel, new() { var m = new TSpecificModel(); Add(CreateViewModel(m)); } }
在这种情况下,我只是让模型暴露了ObservableCollection
而不是List
。 没有什么特别的理由,为什么它不应该。 ObservableCollection
位于System
程序集的System.Collections.ObjectModel
命名空间中,所以不存在任何不合理的额外依赖关系,无论如何你几乎肯定有System
。 List
在mscorlib
,但是这与任何事物一样是一个历史的人造物。
这大大简化了模型 – 视图模型的交互,我看不出一个不这样做的理由,在模型上使用List
s只会产生许多不愉快的样板代码。 毕竟,你对事件感兴趣。
另外,为什么你的HouseVM
包装一个ObservableCollection<PeopleVM>
,而不是ObservableCollection<People>
? 虚拟机是绑定到视图,所以我会认为无论绑定到你的ObservableCollection<PeopleVM>
实际上对People
兴趣,否则你绑定绑定内,或者有一个特定的原因,为什么这是有用的? 我通常不会有虚拟机暴露其他虚拟机,但也许这只是我。
编辑关于库/ WCF
我不明白为什么在一个库中有一个模型,或者甚至是由WCF服务器暴露出来,都会影响它是否引发事件,这对我来说似乎是完全有效的(显然,WCF服务不会直接暴露事件) 。 如果你不喜欢这个,我认为你被困在不得不链接多个更新,但我不知道你是否真的只是手动做同样的工作,事件会在ObservableCollection
做,除非我误解了一些的。
就个人而言,就像我说的那样,我会保持简单的虚拟机,让他们暴露最低限度,而不是暴露其他虚拟机。 它可能需要重新devise,并使某些部分有点痛苦(例如, Converter
,但是,最后你会得到一个简单,易于pipe理的devise,一些简单的处理刺激边缘。
在我看来,你现在的路线将会非常复杂,而且最重要的是很难遵循…但是,YMMV,这只是我的经验:)
也许把一些逻辑转移到显式服务可能有帮助?