【问题标题】:WPF List of ViewModels bound to list of Model objectsWPF 视图模型列表绑定到模型对象列表
【发布时间】:2011-02-20 15:35:15
【问题描述】:

在模型中,我有:

public ObservableCollection<Item> Items { get; private set; }

在 ViewModel 中,我有一个对应的 ItemViewModel 列表。我希望这个列表可以双向绑定到模型的列表:

public ObservableCollection<ItemViewModel> ItemViewModels ...

在 XAML 中,我将绑定(在本例中为 TreeView)到 ItemViewModels 属性。

我的问题是,上面显示的 ViewModel 中的“...”中有什么内容?我希望用一两行代码来绑定这两个 ObservableCollections(提供要为每个模型对象构造的 ViewModel 的类型)。但是,我担心的是需要一堆代码来处理 Items.CollectionChanged 事件并通过根据需要构建 ViewModels 来手动更新 ItemViewModels 列表,而相应的相反将根据对 ItemViewModels 的更改更新 Items 集合。

谢谢!

埃里克

【问题讨论】:

    标签: c# wpf xaml observablecollection mvvm


    【解决方案1】:

    是的,您的担心是真的,您必须封装所有 ObservableCollection 功能。

    不过,我的回归问题是,您为什么要在已经看起来不错的模型周围使用视图模型包装器?如果您的数据模型基于一些不可绑定的业务逻辑,则视图模型很有用。通常,此业务/数据层有一种或两种方式来检索数据并通知外部观察者其更改很容易由视图模型处理并转换为ObservableCollection 的更改。事实上,在 .NET 3.5 中,ObservableCollectionWindowsBase.dll 的一部分,因此通常一开始就不会在数据模型中使用它。

    我的建议是填充/修改ObservableCollection 的逻辑应该从您的数据模型移动到视图模型,或者您应该直接绑定到您当前调用数据模型的层并直接调用它。一个视图模型。

    您显然可以编写一个辅助类,它将使用一些转换器 lambda(从 ItemItemViewModel 并向后)同步两个集合,并在这样的地方使用它(确保您正确处理项目的唯一性) ,但是恕我直言,这种方法会产生大量的包装类,并且每一层都会减少功能并增加复杂性。这与 MVVM 目标完全相反。

    【讨论】:

      【解决方案2】:

      您可以使用以下类:

      public class BoundObservableCollection<T, TSource> : ObservableCollection<T>
      {
          private ObservableCollection<TSource> _source;
          private Func<TSource, T> _converter;
          private Func<T, TSource, bool> _isSameSource;
      
          public BoundObservableCollection(
              ObservableCollection<TSource> source,
              Func<TSource, T> converter,
              Func<T, TSource, bool> isSameSource)
              : base()
          {
              _source = source;
              _converter = converter;
              _isSameSource = isSameSource;
      
              // Copy items
              AddItems(_source);
      
              // Subscribe to the source's CollectionChanged event
              _source.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_source_CollectionChanged);
          }
      
          private void AddItems(IEnumerable<TSource> items)
          {
              foreach (var sourceItem in items)
              {
                  Add(_converter(sourceItem));
              }
          }
      
          void _source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
          {
              switch (e.Action)
              {
                  case NotifyCollectionChangedAction.Add:
                      AddItems(e.NewItems.Cast<TSource>());
                      break;
                  case NotifyCollectionChangedAction.Move:
                      // Not sure what to do here...
                      break;
                  case NotifyCollectionChangedAction.Remove:
                      foreach (var sourceItem in e.OldItems.Cast<TSource>())
                      {
                          var toRemove = this.First(item => _isSameSource(item, sourceItem));
                          this.Remove(toRemove);
                      }
                      break;
                  case NotifyCollectionChangedAction.Replace:
                      for (int i = e.NewStartingIndex; i < e.NewItems.Count; i++)
                      {
                          this[i] = _converter((TSource)e.NewItems[i]);
                      }
                      break;
                  case NotifyCollectionChangedAction.Reset:
                      this.Clear();
                      this.AddItems(_source);
                      break;
                  default:
                      break;
              }
          }
      }
      

      如下使用:

      var models = new ObservableCollection<Model>();
      var viewModels =
          new BoundObservableCollection<ViewModel, Model>(
              models,
              m => new ViewModel(m), // creates a ViewModel from a Model
              (vm, m) => vm.Model.Equals(m)); // checks if the ViewModel corresponds to the specified model
      

      BoundObservableCollection 将在 ObservableCollection 发生更改时更新,但不会反过来(您必须重写一些方法才能做到这一点)

      【讨论】:

      • 谢谢!我知道有人已经完成了封装此功能的工作。一个问题:我看到您在更改操作为 Remove 时使用 isSameSource。为什么不直接使用 e.OldStartingIndex 和 e.OldItems.Count: for (int i = 0; i
      • @Eric,是的,它可以工作,而且实际上会更好......如果您确定两个集合的顺序相同。由于我没有处理 NotifyCollectionChangedAction.Move 案例,因此不能保证...
      • 关于 move 为什么不直接做一个 this.Move(e.OldStartingIndex,e.NewStartingIndex);???这样,两个集合将以相同的方式排序。即使在通常不需要的情况下,如果两个列表保持相同的顺序,也更容易调试。
      猜你喜欢
      • 2013-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多