MVVM 和 VM 集合
Posted
技术标签:
【中文标题】MVVM 和 VM 集合【英文标题】:MVVM and collections of VMs 【发布时间】:2013-03-27 15:17:29 【问题描述】:一个常见的场景:一个包含项目模型集合的模型。 例如,房子里有一群人。
如何为 MVVM 正确构建此结构 - 特别是关于通过添加和删除更新 Model 和 ViewModel 集合?
模型House
包含模型People
的集合(通常是List<People>
)。
视图模型HouseVM
包含它包装的 House 对象和视图模型PeopleVM
(ObservableCollection<PeopleVM>
) 的 ObservableCollection。请注意,我们最终会看到 HouseVM 拥有两个集合(需要同步):
1.HouseVM.House.List<People>
2.HouseVM.ObservableCollection<PeopleVM>
当房子更新为新的人(添加)或人离开(删除)时,现在必须在模型房子人集合和VM HouseVM PeopleVM ObservableCollection的两个集合中处理该事件。
这个结构是正确的 MVVM 吗? 有没有办法避免对 Adds 和 Removes 进行双重更新?
【问题讨论】:
我真的很想听听答案,因为这是一个我永远无法真正摆脱的问题。 【参考方案1】:您的一般方法是完美的 MVVM,让 ViewModel 公开其他 ViewModel 的集合是一种非常常见的场景,我到处都在使用它。我不建议直接在 ViewModel 中公开项目,就像 nicodemus13 所说的那样,因为您最终会将视图绑定到模型,而在您的集合项目之间没有 ViewModels。所以,第一个问题的答案是:是的,这是有效的 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));
【讨论】:
这看起来是个好方法——我想这就是我所追求的——让我接受它。 我一直在尝试实现类似的模式,但我从未对结果感到满意。你在这里给了我们一个很棒的实现。非常感谢! @Marc:你真的超越了这里。我在 *** 上见过的最好的答案之一。干得好。 @Marc 是最好的 ***s 之一! @VijayChavda 这有点学术,但该事件被覆盖和密封以避免在构造函数中调用虚拟成员。更多信息请点击此处:***.com/questions/119506/…。覆盖只是确保 ViewModelCollection 的继承实现不会破坏事件,从而破坏整个功能。如前所述,它几乎没有实际意义,但在学术上,密封事件是有意义的,而且不会造成伤害。【参考方案2】:在这种情况下,我只是让模型暴露ObservableCollection
s 而不是List
s。没有特别的理由不应该这样做。 ObservableCollection
位于 System
程序集的 System.Collections.ObjectModel
命名空间中,因此没有不合理的额外依赖项,无论如何您几乎肯定有 System
。 List
在 mscorlib
中,但这与任何东西一样都是历史文物。
这极大地简化了模型-视图模型的交互,我找不到不这样做的理由,在模型上使用List
s 只会创建许多令人不快的样板代码。毕竟你对这些事件很感兴趣。
另外,为什么你的HouseVM
包裹ObservableCollection<PeopleVM>
,而不是ObservableCollection<People>
?虚拟机用于绑定到视图,所以我认为绑定到您的ObservableCollection<PeopleVM>
的任何内容实际上都对People
感兴趣,否则您是绑定内绑定,或者有特定原因为什么这很有用?我通常不会让 VM 公开其他 VM,但也许这只是我。
编辑库/WCF
我不明白为什么在库中拥有模型,甚至由 WCF 服务器公开会影响它们是否引发事件,这对我来说似乎完全有效(显然 WCF 服务不会公开直接事件)。如果您不喜欢这样,我认为您不得不链接多个更新,但我想知道您是否实际上只是手动执行与ObservableCollection
中的事件相同的工作,除非我已经误解了一些。
就我个人而言,就像我说的那样,我会保持 VM 的简单性,并让它们暴露最低限度,而不是暴露其他 VM。它可能需要一些重新设计并使某些部分有点痛苦(例如Converter
s,但是,您最终会得到一个简单、易于管理的设计,边缘有一些易于处理的刺激。
在我看来,您当前的路线很快就会变得非常复杂,最重要的是,难以遵循……但是,YMMV,这只是我的经验:)
也许将一些逻辑转移到显式服务可能会有所帮助?
【讨论】:
给模型一个 ObserbableCollection以上是关于MVVM 和 VM 集合的主要内容,如果未能解决你的问题,请参考以下文章
在 MVVM 中将 Observable 集合绑定到 wrappanel
WPF 利用附加属性创建FreezableCollection集合和反射实现控件参数以MVVM模式传递
有没有办法在不通知 ItemsSource 集合的情况下使用 ObservableCollection 和 MVVM 刷新 WPF DataGrid?