MVVM同步集合

Jak*_*son 45 c# wpf mvvm

是否有一种标准化的方法可以将一组Model对象与C#和WPF中匹配的ModelView对象的一组同步?我正在寻找某种类,它会保持以下两个集合同步,假设我只有几个苹果,我可以将它们全部留在内存中.

另一种说法,我想确保如果我将Apple添加到Apples集合中,我想将AppleModelView添加到AppleModelViews集合中.我可以通过收听每个集合的CollectionChanged事件来编写自己的事件.这似乎是一种常见的情况,比我聪明的人定义了"正确的方法"来做到这一点.

public class BasketModel
{
    public ObservableCollection<Apple> Apples { get; }
}

public class BasketModelView
{
    public ObservableCollection<AppleModelView> AppleModelViews { get; }
}
Run Code Online (Sandbox Code Playgroud)

Sam*_*ell 64

我使用延迟构建的自动更新集合:

public class BasketModelView
{
    private readonly Lazy<ObservableCollection<AppleModelView>> _appleViews;

    public BasketModelView(BasketModel basket)
    {
        Func<AppleModel, AppleModelView> viewModelCreator = model => new AppleModelView(model);
        Func<ObservableCollection<AppleModelView>> collectionCreator =
            () => new ObservableViewModelCollection<AppleModelView, AppleModel>(basket.Apples, viewModelCreator);

        _appleViews = new Lazy<ObservableCollection<AppleModelView>>(collectionCreator);
    }

    public ObservableCollection<AppleModelView> Apples
    {
        get
        {
            return _appleViews.Value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用以下内容ObservableViewModelCollection<TViewModel, TModel>:

namespace Client.UI
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics.Contracts;
    using System.Linq;

    public class ObservableViewModelCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
    {
        private readonly ObservableCollection<TModel> _source;
        private readonly Func<TModel, TViewModel> _viewModelFactory;

        public ObservableViewModelCollection(ObservableCollection<TModel> source, Func<TModel, TViewModel> viewModelFactory)
            : base(source.Select(model => viewModelFactory(model)))
        {
            Contract.Requires(source != null);
            Contract.Requires(viewModelFactory != null);

            this._source = source;
            this._viewModelFactory = viewModelFactory;
            this._source.CollectionChanged += OnSourceCollectionChanged;
        }

        protected virtual TViewModel CreateViewModel(TModel model)
        {
            return _viewModelFactory(model);
        }

        private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
            case NotifyCollectionChangedAction.Add:
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    this.Insert(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i]));
                }
                break;

            case NotifyCollectionChangedAction.Move:
                if (e.OldItems.Count == 1)
                {
                    this.Move(e.OldStartingIndex, e.NewStartingIndex);
                }
                else
                {
                    List<TViewModel> items = this.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToList();
                    for (int i = 0; i < e.OldItems.Count; i++)
                        this.RemoveAt(e.OldStartingIndex);

                    for (int i = 0; i < items.Count; i++)
                        this.Insert(e.NewStartingIndex + i, items[i]);
                }
                break;

            case NotifyCollectionChangedAction.Remove:
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);
                break;

            case NotifyCollectionChangedAction.Replace:
                // remove
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);

                // add
                goto case NotifyCollectionChangedAction.Add;

            case NotifyCollectionChangedAction.Reset:
                Clear();
                for (int i = 0; i < e.NewItems.Count; i++)
                    this.Add(CreateViewModel((TModel)e.NewItems[i]));
                break;

            default:
                break;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 应该从这段代码中删除 `Contracts` 和 `Lazy` 的使用。它给这个答案带来了不必要的复杂性.. (2认同)

Den*_*nis 9

我可能不完全理解您的要求,但是我处理类似情况的方式是在ObservableCollection上使用CollectionChanged事件,并根据需要简单地创建/销毁视图模型.

void OnApplesCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{    
  // Only add/remove items if already populated. 
  if (!IsPopulated)
    return;

  Apple apple;

  switch (e.Action)
  {
    case NotifyCollectionChangedAction.Add:
      apple = e.NewItems[0] as Apple;
      if (apple != null)
        AddViewModel(asset);
      break;
    case NotifyCollectionChangedAction.Remove:
      apple = e.OldItems[0] as Apple;
      if (apple != null)
        RemoveViewModel(apple);
      break;
  }

}
Run Code Online (Sandbox Code Playgroud)

在ListView中添加/删除大量项目时可能会出现一些性能问题.

我们通过以下方式解决了这个问题:扩展ObservableCollection以使用AddRange,RemoveRange,BinaryInsert方法并添加通知其他人正在更改集合的事件.与扩展的CollectionViewSource一起,当集合被更改时临时断开源,它可以很好地工作.

HTH,

丹尼斯