wpf MVVM 和 VM 集合

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15830008/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:28:53  来源:igfitidea点击:

MVVM and collections of VMs

wpfmvvmcollectionsviewmodel

提问by Ricibob

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?

如何为 MVVM 正确构建这个结构——特别是关于通过添加和删除更新 Model 和 ViewModel 集合?

Model Housecontains a collection of model People(normally a List<People>).
View model HouseVMcontains 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>

模型House包含模型的集合People(通常为 a List<People>)。
视图模型HouseVM包含它包装的 House 对象和视图模型的 ObservableCollection PeopleVM( ObservableCollection<PeopleVM>)。请注意,我们最终使用 HouseVM 持有两个集合(需要同步):
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 ANDthe the VM HouseVM PeopleVM ObservableCollection.

当 House 用新的 People(添加)或 People leave(删除)更新时,现在必须在 Model House People 集合VM HouseVM PeopleVM ObservableCollection这两个集合中处理该事件。

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

这种结构是正确的 MVVM 吗?
有没有办法避免对添加和删除进行双重更新?

回答by Marc

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.

您的一般方法是非常好的 MVVM,让 ViewModel 公开其他 ViewModel 的集合是一种非常常见的场景,我到处都在使用。我不建议直接在 ViewModel 中公开项目,就像 nicodemus13 所说的那样,因为您最终将视图绑定到模型,而您的集合项目之间没有 ViewModel。因此,您的第一个问题的答案是:是的,这是有效的 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.

您在第二个问题中要解决的问题是房屋模型中的人物模型列表与房屋 ViewModel 中的人物 Vie​​wModel 列表之间的同步。您必须手动执行此操作。所以,没有没有办法避免这种情况。

enter image description here

在此处输入图片说明

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 CollectionChangedevent in your ViewModelCollection.

您可以做什么:实现自定义ObservableCollection<T>, ViewModelCollection<T>,将其更改推送到基础集合。要获得双向同步,请将模型的集合也设为 ObservableCollection<> 并注册到CollectionChanged您的 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...

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

回答by nicodemus13

In this situation I simply make the model expose ObservableCollections rather than Lists. There's no particular reason why it shouldn't. The ObservableCollectionis in the System.Collections.ObjectModelnamespace of the Systemassembly, so there's no unreasonable extra dependencies, you almost certainly have Systemanyway. Listis in mscorlib, but that's as much a historical artefact as anything.

在这种情况下,我只是让模型公开ObservableCollections 而不是Lists。没有特别的理由为什么不应该。该ObservableCollection是在System.Collections.ObjectModel该命名空间System组装,所以有没有不合理的额外的依赖,你几乎可以肯定System反正。 Listmscorlib,但这与任何事物一样都是历史文物。

This simplifies the model-viewmodel interactions massively, I can't see a reason not to do it, using Lists on the model just creates lots of unpleasant boiler-plate code. You are interested in the events, after all.

这大大简化了模型-视图模型的交互,我看不出有什么理由不这样做,List在模型上使用s 只会产生很多令人不快的样板代码。毕竟,你对这些事件感兴趣。

Also, why is your HouseVMwrapping an ObservableCollection<PeopleVM>, rather than ObservableCollection<People>? VMs are for binding to views, so I would think that whatever is binding to your ObservableCollection<PeopleVM>is actually interested in People, otherwise you're binding-within-a-binding, or is there a specific reason why this is useful? I wouldn't generally have a VM expose other VMs, but maybe that's just me.

另外,为什么您要HouseVM包装ObservableCollection<PeopleVM>, 而不是 ObservableCollection<People>?VM 用于绑定到视图,因此我认为绑定到您的任何内容ObservableCollection<PeopleVM>实际上都对 感兴趣People,否则您将在绑定中绑定,或者是否有特定原因说明这很有用?我通常不会让 VM 暴露其他 VM,但也许这只是我。

Edit about libraries/WCF

编辑关于库/WCF

I don't see why having a model in a library, or even exposed by a WCF-server should affect whether they raise events or not, it seems perfectly valid to me (obviously the WCF-service won't expose the events directly). If you don't like this, I think you're stuck with having to chain multiple updates, though I wonder if you're actually just manually doing the same work as the event would do in an ObservableCollection, unless I've misunderstood some of it.

我不明白为什么在库中拥有模型,甚至由 WCF 服务器公开会影响它们是否引发事件,这对我来说似乎完全有效(显然 WCF 服务不会直接公开事件) . 如果你不喜欢这样,我认为你不得不链接多个更新,尽管我想知道你是否真的只是手动做与事件在 中所做的相同的工作ObservableCollection,除非我误解了一些它。

Personally, like I said, I'd keep the VMs simple, and have them expose the minimum and not expose other VMs. It can take some redesign and make certain parts a bit of a pain (e.g. Converters, however, you end up with a simple, easy-to-manage design with some simple-to-handle irritations on the edges.

就我个人而言,就像我说的那样,我会保持 VM 的简单性,并让它们公开最少的内容,而不公开其他 VM。它可能需要一些重新设计,并使某些部分有点麻烦(例如Converter,但是,您最终会得到一个简单、易于管理的设计,但边缘有一些易于处理的刺激。

It seems to me that your current route is going to end up very complex rather quickly and, most importantly, awkward to follow... However, YMMV, it's just my experience :)

在我看来,您当前的路线将很快变得非常复杂,而且最重要的是,难以遵循...但是,YMMV,这只是我的经验:)

Perhaps moving some of the logic to explicit services might help?

也许将某些逻辑移至显式服务可能会有所帮助?