【问题标题】:wpf mvvm : Why duplicating model collections in view models when binding to a treeview? how to avoid it?wpf mvvm:绑定到树视图时为什么要在视图模型中复制模型集合?如何避免?
【发布时间】:2016-11-11 06:11:16
【问题描述】:

我的模型中有一个由星座和实体组成的 2 级层次结构,托管在根对象中,我想在 TreeView 中显示它们。

Root object L Constellations L Entities

我的RootObjectViewModel 暴露了我的根对象。在那方面,我完全同意 MVVM best practices 中的 Bryan Lagunas 的观点,即模型对象应该由视图模型公开,而不是做外观。顺便说一句,很棒的视频,真的值得花时间。

我到处读到TreeView.ItemsSource 应该映射到视图模型的集合,也就是说,对于我的星座集合,我的RootObjectViewModel 应该提供ConstellationViewModel 的集合。 我担心的是,如果我的星座集合和其中的实体是实时的,也就是说,如果添加、更改(它们的顺序)或删除了某些项目,我必须在我的 ViewModel 集合中手动反映这些更改。

我会发现将 ItemsSource 映射到 Constellation 本身的集合会更优雅,这样模型中的任何更改都不会重复反映,然后有一些转换器来映射 TreeViewItems.DataContext到基于星座模型的ConstellationViewModel

如果这还不够清楚,我可以用一些示例代码来澄清这一点。

是否有人遇到过同样的问题和/或尝试实施此问题?

提前感谢您的帮助。

塞德里克

【问题讨论】:

    标签: wpf mvvm collections treeview


    【解决方案1】:

    这取决于。如果您的模型具有视图所需的属性,并且视图可以在用户单击时直接更改它们,那么暴露模型就可以了。

    但是,例如,如果您的模型是只读的并且需要调用服务来应用更改,则您必须将其包装在视图模型中,以便为视图提供可写属性。

    【讨论】:

    • 当然,我的意思不是完全摆脱视图模型。这就是为什么我提到一个转换器,比如说 ModelToViewModel 转换器,它将返回给定模型的 ViewModel,它将被分配到TreeViewItem 数据上下文,假设可以提供转换器...
    • 转换器很好,但是如果视图模型创建这么简单(new ViewModel( model )),我想你不会从中获得太多收益。如果你有很多依赖,你会为每种类型获得专门的转换器,它们可以完成视图模型的工作。虽然它可能在向前的方向上工作,但反过来会有点难看,因为你的转换器最终会创建/添加新的模型实例......
    • 我对 MVVM 的理解是 GUI 不应该管理数据,而是通过命令和事件通知视图模型,然后由模型做出反应。因此,我不太害怕其他方式,因为模型会生成新项目并填充集合,这些集合将在前进的道路上适当地显示。现在我同意你的观点,这意味着每种模型类型都有一个转换器来返回适当的视图模型。但是,当您在主视图模型的集合中生成它们时,或多或少会发生这种情况。
    • 我想我会启动一个 POC 项目,看看进展如何
    • 让我们保持更新,这个视图模型创建转换器可能是一个非常酷的想法!
    【解决方案2】:

    搞定了!

    开箱即用是不可能的,原因如下:


    可以使用模型集合作为项目源,并使用转换器来获取适当的视图模型,它是TreeViewItem 中的组件。但是没有任何方法可以干扰TreeViewItem 的创建以将转换器应用于其DataContext。这意味着TreeViewItem 的属性无法绑定到 ViewModel。

    换句话说:

    • 如果您想坚持TreeView 的标准行为并且不必处理TreeViewItems 属性,如果您的集合不改变或可以实现ICollectionChanged,并且如果您的模型不改也可以实现IPropertyChanged,配合模型的集合就好了。
    • 如果这些条件中的任何一个被破坏,那么您将不得不构建 ViewModel 的集合并将它们与模型的集合同步。

    现在,我实现了一个名为 ConvertingCollection<Tin, Tout> 的集合类型,它使用原始集合作为输入并将其自己的内容与该输入同步。这只是一个基础课程,有很多改进的方法,但它确实有效。您所要做的就是将此集合用作 VM 属性,设置原始集合和转换器,并将 ItemsSource 绑定到此集合。

    这是完整的代码:

    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.Windows.Data;
    
    namespace TreeViewPOC
    {
      class ConvertingCollection<Tin, Tout> : ObservableCollection<Tout>
      {
        private IValueConverter _converter;
        private bool _isObservableCollection;
        private IEnumerable<Tin> _originalCollection;
        private Dictionary<Tin, Tout> _mapping = new Dictionary<Tin, Tout>();
    
    
        public ConvertingCollection(IValueConverter converter)
        {
          // save parameters
          _converter = converter;
        }
    
        public ConvertingCollection(IEnumerable<Tin> originalCollection, IValueConverter converter)
        {
          // save parameters
          _converter = converter;
          OriginalCollection = originalCollection;
        }
    
        #region Properties
        public IEnumerable<Tin> OriginalCollection
        {
          get
          {
            return _originalCollection;
          }
          set
          {
            if (!value.Equals(_originalCollection))
            {
              // manage older collection
              if (_originalCollection != null && _isObservableCollection)
              {
                (_originalCollection as ObservableCollection<Tin>).CollectionChanged -= originalCollection_CollectionChanged;
                this.Clear();
              }
    
              _originalCollection = value;
    
              // setup original collection information.
              _isObservableCollection = _originalCollection is INotifyCollectionChanged;
              if (_originalCollection != null && _isObservableCollection)
              {
                (_originalCollection as INotifyCollectionChanged).CollectionChanged += originalCollection_CollectionChanged;
                foreach (Tin item in _originalCollection)
                {
                  AddConverted(item);
                }
              }
            }
          }
        }
    
        #endregion
        /// <summary>
        /// Indicates the time in milliseconds between two refreshes.
        /// </summary>
        /// <notes>
        /// When the original collection isn't observable, it must be explored to reflect changes in the converted collection.
        /// </notes>
        // TODO
        //public int RefreshRate { get; set; } = 1000;
    
        /// <summary>
        /// Flushes the collection.
        /// </summary>
        public new void Clear()
        {
          _mapping.Clear();
          base.Clear();
        }
    
        #region Events management
    
        private void originalCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
          switch (e.Action)
          {
            case NotifyCollectionChangedAction.Add:
              foreach (Tin item in e.NewItems)
              {
                AddConverted(item);
              }
              break;
    
            case NotifyCollectionChangedAction.Remove:
              foreach (Tin item in e.OldItems)
              {
                RemoveConverted(item);
              }
              break;
          }
        }
        #endregion
    
        #region Helpers
        /// <summary>
        /// Converts an item and adds it to the collection.
        /// </summary>
        /// <param name="item">The original item.</param>
        private void AddConverted(Tin item)
        {
          Tout converted = (Tout) _converter.Convert(item, typeof(Tout), null, CultureInfo.CurrentCulture);
          _mapping.Add(item, converted);
          this.Add(converted);
        }
    
        /// <summary>
        /// Removes a converted itemfrom the collection based on its original value.
        /// </summary>
        /// <param name="item">The original item.</param>
        private void RemoveConverted(Tin item)
        {
          this.Remove(_mapping[item]);
          _mapping.Remove(item);
        }
        #endregion
      }
    }
    

    我在 Github 上创建了一个名为 MVVMTreeViewPOC 的项目来 POC 这个想法。它运行良好,是显示局限性的好工具。

    塞德里克。

    【讨论】:

    • 这里仅提及另一种同类方法:[stackoverflow.com/questions/7505524/…,但看起来转换是在列表视图项的内容上执行的,因此仍然无法将IsSelected绑定到ViewModel.
    • 另一个重要的信息来源是drwpf.com/blog/category/item-containers,它有助于理解树视图、列表视图等。实际工作。尚未浏览所有文章,但我的理解是,将项目链接到容器的 ItemContainerGenerator 应该是使用转换器的正确位置。但是班级是密封的...
    猜你喜欢
    • 2018-10-25
    • 2011-10-07
    • 1970-01-01
    • 1970-01-01
    • 2012-07-31
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多