首页 文章

MVVM和VM集合

提问于
浏览
41

常见的场景:具有项目模型集合的模型 .
例如,一个有人物集合的房子 .

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

模型 House 包含模型 People (通常为 List<People> )的集合 .
视图模型 HouseVM 包含它包装的House对象和视图模型 PeopleVMObservableCollection<PeopleVM> )的ObservableCollection . 请注意,我们最终会在HouseVM中保存两个集合(需要同步):

  1. HouseVM.House.List<People>
  2. HouseVM.ObservableCollection<PeopleVM>

当House更新为新人(添加)或人员离开(删除)时,现在必须在两个集合中处理该事件,Model House People集合 AND 是VM HouseVM PeopleVM ObservableCollection .

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

2 回答

  • 4

    在这种情况下,我只是让模型暴露 ObservableCollection 而不是 List . 有's no particular reason why it shouldn' t . ObservableCollection 位于 System 程序集的 System.Collections.ObjectModel 名称空间中,因此没有不合理的额外依赖项,无论如何你几乎肯定都有 System . Listmscorlib 中,但这与任何东西一样都是历史人工制品 .

    这大大简化了模型 - 视图模型的交互,我看不出不这样做的理由,在模型上使用 List 只会创建大量令人不快的样板代码 . 毕竟,你对这些活动很感兴趣 .

    另外,为什么你的 HouseVM 包裹 ObservableCollection<PeopleVM> 而不是 ObservableCollection<People> ?虚拟机用于绑定视图,因此我认为绑定到 ObservableCollection<PeopleVM> 的任何内容实际上都对 People 感兴趣,否则你通常会有一个虚拟机暴露其他虚拟机,但也许这只是我 .

    Edit about libraries/WCF

    我不直接暴露事件 . 如果你因为必须链接多个更新而感到困惑,尽管我想知道你是否真的只是手动执行与事件在_240073中相同的工作,除非我误解了其中的一些 .

    就个人而言,就像我说的那样,我会让VM保持简单,并让它们暴露最小值而不暴露其他虚拟机 . 它可能需要一些重新设计,并使某些部件有点痛苦(例如, Converter s,然而,你最终得到一个简单,易于管理的设计,边缘有一些易于处理的烦恼 .

    在我看来,你现在的路线很快会变得非常复杂,最重要的是,尴尬地跟随...但是,YMMV,这只是我的经验:)

    也许将一些逻辑转移到显式服务可能会有所帮助?

  • 47

    你的一般方法是完美的MVVM,让ViewModel公开其他ViewModel的集合是一个非常常见的场景,我在这里使用它 . 我不建议直接在ViewModel中公开项目,比如nicodemus13说,因为你最终将视图绑定到没有ViewModel的模型,用于你的集合项目 . 所以,你的第一个问题的答案是:是的,这是有效的MVVM .

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

    enter image description here

    你可以做什么:实现一个自定义 ObservableCollection<T>ViewModelCollection<T> ,它也将它's changes to an underlying collection. To get two way synching, make the model'的集合推送到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));
        }
    }
    

相关问题