MVVM和虚拟机的集合(MVVM and collections of VMs)

2019-08-19 18:25发布

一个常见的塞纳里奥:与项目模型集合的模型。
例如,与人的集合房子。

如何正确结构此为MVVM -格外关于更新模型和视图模型收藏与添加和删除?

示范House中含有模型的集合People (通常是一个List<People> )。
视图模型HouseVM包含内务对象,它包装和视图模型的一个ObservableCollection PeopleVMObservableCollection<PeopleVM> 请注意,我们会在这里结束与HouseVM拿着两个集合(即需要同步):
1. HouseVM.House.List<People>
2. HouseVM.ObservableCollection<PeopleVM>

当房子与新人们更新(添加)或人离开(删除)该事件现在必须两个集合模型房子的人收集虚拟机HouseVM PeopleVM的ObservableCollection处理。

这是纠正结构MVVM?
反正是有,以避免做的添加和删除双更新?

Answer 1:

你一般的方法是完全正常的MVVM,有一个ViewModel揭露其他的ViewModels的集合是一个很常见的场景,我使用所有的地方。 我不建议直接在视图模型暴露的物品,像nicodemus13说,当你结束了你的观点为您收集的物品之间的结合模式没有的ViewModels。 所以,回答你的第一个问题是:是的,这是有效的MVVM。

你在你的第二个问题正在解决的问题是人车型在你的房子模型的列表和人民的ViewModels在你的房子视图模型的列表之间的同步。 你必须手动完成。 因此,无论有没有办法避免这种情况。

你可以做什么:实现自定义ObservableCollection<T> ViewModelCollection<T>它推动它改变了底层集合。 为了获得双向synching,使模型的收藏一个ObservableCollection <>也和注册到CollectionChanged在ViewModelCollection事件。

这是我的实现。 它采用了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));
    }
}


Answer 2:

在这种情况下,我只是做了模型暴露ObservableCollection真是让人不是List秒。 没有什么特别的原因,为什么它不应该。 的ObservableCollection是在System.Collections.ObjectModel的命名空间System组装,所以有没有不合理的额外的依赖,你几乎可以肯定有System反正。 List是在mscorlib ,但这是尽可能多的历史假象的东西。

这简化了模型-视图模型的相互作用大规模,我看不出一个理由不这样做,用List S于模型只是创建很多不愉快的锅炉板代码。 你感兴趣的事件,毕竟。

另外,为什么是你HouseVM包装的ObservableCollection<PeopleVM>而不是ObservableCollection<People> ? 虚拟机是结合意见,所以我认为,无论是绑定到您ObservableCollection<PeopleVM>实际感兴趣People ,否则你结合中之结合,或者是有具体原因,这是有用的? 我一般不会有VM揭露其他VM,但也许这只是我。

编辑约库/ WCF

我不明白为什么在库中有一个模型,甚至一个WCF服务器暴露应该影响他们是否引发事件与否,似乎完全有效的,我(显然WCF服务不会直接暴露事件) 。 如果你不喜欢这样,我想你坚持不必链多次更新,虽然我不知道你实际上只是手工做同样的工作,当事件在一个会做会ObservableCollection ,除非我误解了一些它。

就个人而言,就像我说的,我会保持简单的虚拟机,并让他们暴露在最小和不公开的其他虚拟机。 它可以采取一些重新设计,使某些部位有点疼痛(如Converter S,不过,你最终得到一个简单,易于管理与边缘上的一些简单到手柄刺激设计。

在我看来,你的当前路由要结束了非常复杂的相当快,最重要的,笨拙跟随......然而,情况因人而异,这只是我的经验:)

也许一些移动的逻辑明确的服务可以帮助?



文章来源: MVVM and collections of VMs