MVVM和VM集合 [英] MVVM and collections of VMs

查看:190
本文介绍了MVVM和VM集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

常见的情景:具有项目模型集合的模型。

例如一个拥有People集合的房子。



如何正确地为MVVM结构化 - 特别是关于通过添加和删除更新Model和ViewModel集合?



模型 包含模型 People (通常为 List< People> )的集合。 >
View $ HouseVM 包含它包装的House对象和一个ObservableCollection视图模型 PeopleVM code> ObservableCollection< PeopleVM> )。注意,我们最终在这里与HouseVM持有两个集合(需要同步):

1. HouseVM.House.List< People>

2. HouseVM.ObservableCollection< PeopleVM>



添加)或人离开(删除)该事件现在必须在两个集合中处理模型馆人员集合 AND VM HouseVM PeopleVM ObservableCollection。



这个结构是否正确的MVVM?

有没有避免对Adds和Removes进行双重更新?



你在第二个问题解决的问题是人类模型列表之间的同步在你的房子模型和人物列表ViewModel在你的房子ViewModel。您必须手动执行此操作。因此,没有办法避免这种情况。





您可以做什么:实现自定义 ObservableCollection< T> ViewModelCollection< T> ,它将更改推送到底层集合。要获得双向同步,使模型的集合也成为一个ObservableCollection<>,并注册到ViewModelCollection中的 CollectionChanged 事件。



这是我的实现。它使用一个ViewModelFactory服务等等,但只是看一般的主体。我希望它有帮助...

  ///< summary> 
///将可变集合推送到相关模型集合的ViewModels的可观察集合
///< / summary>
///< typeparam name =TViewModel>集合中的ViewModels类型< / typeparam>
///< typeparam name =TModel>底层集合中的模型类型< / typeparam>
public class VmCollection< TViewModel,TModel> :ObservableCollection< TViewModel>
其中TViewModel:class,IViewModel
其中TModel:class

{
private readonly object _context;
private readonly ICollection< TModel> _楷模;
private bool _synchDisabled;
private readonly IViewModelProvider _viewModelProvider;

///< summary>
///构造函数
///< / summary>
///< param name =models>要与< / param>同步的模型列表
///< param name =viewModelProvider>< / param>
///< param name =context>< / param>
///< param name =autoFetch>
///确定ViewModel的集合是否应该是
///从构造的模型集合中获取
///< / param>
public VmCollection(ICollection< TModel> models,IViewModelProvider viewModelProvider,object context = null,bool autoFetch = true)
{
_models = models;
_context = context;

_viewModelProvider = viewModelProvider;

//注册同步的更改处理
//从ViewModels到模型
CollectionChanged + = ViewModelCollectionChanged;

//如果模型集合是可观察的寄存器改变
//处理从模型到ViewModels的同步
if(models是ObservableCollection< TModel>)
{
var observableModels = models as ObservableCollection< TModel> ;;
observableModels.CollectionChanged + = ModelCollectionChanged;
}


// Fecth ViewModels
if(autoFetch)FetchFromModels();
}

///< summary>
/// ViewModelCollection的CollectionChanged事件
///< / summary>
public override sealed event NotifyCollectionChangedEventHandler CollectionChanged
{
add {base.CollectionChanged + = value; }
remove {base.CollectionChanged - = value; }
}

///< summary>
///从模型集合加载VM集合
///< / summary>
public void FetchFromModels()
{
//停用更改push
_synchDisabled = true;

//清除集合
Clear();

//为每个模型创建和添加新的VM
foreach(_models中的var模型)
AddForModel(model);

//重新激活更改push
_synchDisabled = false;
}

private void ViewModelCollectionChanged(object sender,NotifyCollectionChangedEventArgs e)
{
//如果同步在内部被禁用则返回
if(_synchDisabled)return;

//禁用同步
_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(e.OldItems.OfType< IViewModel>(var。m))中的var m选择(v => v.Model).OfType< TModel> )
_models.Remove(m);
break;

case NotifyCollectionChangedAction.Reset:
_models.Clear();
foreach(在e.NewItems.OfType< IViewModel>()中的var m。)Select(v => v.Model).OfType< TModel>())
_models.Add
break;
}

//启用同步
_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(e.OldItems.OfType< TModel>()中的var m)
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>
///为指定的模型实例添加一个新的ViewModel
///< / summary>
///< param name =model>为< / param>创建ViewModel的模型
public void AddForModel(TModel model)
{
Add(CreateViewModel(model));
}

///< summary>
///使用指定类型的新模型实例添加一个新的ViewModel
///这是ModelType或派生自模型类型
///< / summary>
///< typeparam name =TSpecificModel>要为< / typeparam>添加ViewModel的模型类型。
public void AddNew< TSpecificModel>()其中TSpecificModel:TModel,new()
{
var m = new TSpecificModel
Add(CreateViewModel(m));
}
}


A common senario: A model with a collection of item models.
E.g a House with a collection of People.

How to structure this correctly for MVVM - particulary with regard to updating the Model and ViewModel collections with additions and deletes?

Model House contains a collection of model People (normally a List<People>).
View model HouseVM contains the House object that it wraps and an ObservableCollection of view model PeopleVM (ObservableCollection<PeopleVM>). Note that we end up here with the HouseVM holding two collections (that require syncing):
1. HouseVM.House.List<People>
2. HouseVM.ObservableCollection<PeopleVM>

When the House is updated with new People (add) or People leave (remove) that event must now be handled in both collections the Model House People collection AND the the VM HouseVM PeopleVM ObservableCollection.

Is this structure correct MVVM?
Is there anyway to avoid having to do the double update for Adds and Removes?

解决方案

Your general approach is perfectly fine MVVM, having a ViewModel exposing a collection of other ViewModels is a very common scenario, which I use all over the place. I would not recommend exposing items directly in a ViewModel, like nicodemus13 said, as you end up with your view binding to models without ViewModels in between for your collection's items. So, the answer to your first question is: Yes, this is valid MVVM.

The problem you are addressing in your second question is the synchronization between the list of people models in your house model and the list of people ViewModels in your house ViewModel. You have to do this manually. So, no there is no way to avoid this.

What you can do: Implement a custom ObservableCollection<T>, ViewModelCollection<T>, which pushes it's changes to an underlying collection. To get two way synching, make the model's collection an ObservableCollection<> too and register to the CollectionChanged event in your ViewModelCollection.

This is my implementation. It uses a ViewModelFactory service and so on, but just have a look at the general principal. I hope it helps...

/// <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));
    }
}

这篇关于MVVM和VM集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆