【问题标题】:Creating a custom collection that can be bound to a DataGrid创建可以绑定到 DataGrid 的自定义集合
【发布时间】:2010-11-11 12:20:15
【问题描述】:

我在一家建筑公司工作,我正在为 3D 建模程序创建一个插件来协助设计。我有一个 Building 类和一个 Floor 类。该建筑物包含对 FloorList 楼层集合的引用。我试图弄清楚 FloorList 集合的基础是什么,这样我就可以最大限度地减少创建一个编辑集合的界面所需的工作量。

Floor 系列代表了一系列相互堆叠的建筑楼层。每个 Floor 都有一个可读写的 Floor.Height 属性,以及一个只读且通过合计楼层来设置的 Floor.Elevation 属性低于当前楼层的高度。因此,每当在集合中添加、删除、移动或更改 Floor 时,都需要更新 Floor.Elevation 属性。

另外,我想创建一个 UI 来编辑这个集合。我正在考虑使用 DataGrid 控件,其中每个 Floor 与它的 Height 和其他属性作为控件的一行列出。用户应该能够使用控件添加、删除和重新排序楼层。我希望将其设置为尽可能简单和灵活。这意味着我希望能够简单地将 Floors 集合绑定到 DataGrid 并根据 Floor 的属性填充 DataGrid 的列 类。如果可能的话,我希望能够利用 DataGrid 控件的内置添加/删除 UI 界面,而不必在我的集合和 DataGrid 之间设置一堆事件关系。

为了让事情在未来变得更加复杂,我需要能够允许用户动态地向 Floors 添加自定义属性,我希望他们能够在其中查看和编辑DataGrid 也是如此。我想我最终会通过让 Floor 类实现 IExtenderProvider 来做到这一点。所以最终 DataGrid 看起来像这样:

初始属性 未来自定义用户属性 海拔高度 P​​rogramType UnitType UnitCount 15' 70' 豪华住宅 5 15' 55' 豪华住宅 5 15' 40' 住宅预算 10 20' 20' 零售 N/A 2 20' 0' 零售 N/A 3

我现在的问题是我的 FloorList 集合应该以什么为基础才能实现此功能?我正在考虑的选项如下。

1) 继承自 List(Floor)

  • 添加/删除等方法不是虚拟的,因此我不能不覆盖它们来更新高程

2) 实现 IList(Floor)

  • OnChange 事件未内置,因此如果列表发生更改,DataGrid 将不会更新(我认为?)

  • 我认为这可能是最好的选择,但我需要做些什么来确保对 FloorList 集合或 DataGrid 的更改彼此同步?

3) 继承自 BindingList(Floor)

  • Add/Remove 之类的方法不是虚拟的,因此我无法修改它们来更新楼层标高。

4) 实现 IBindingList

  • IBindinglist 不是通用的,我只希望我的集合包含 Floor 对象

【问题讨论】:

    标签: c# list collections binding bindinglist


    【解决方案1】:

    实现 IList(Floor),并让您的新集合也实现 INotifyPropertyChanged 接口。

    【讨论】:

      【解决方案2】:

      您应该为您的集合使用 BindingList 或实现 IBindingList,因为这将通知 DataGridView 列表中的任何更改。

      然后为 Floor 类实现 INotifyPropertyChanged 接口,这将允许您的各个 Floor 项目和 DataGridView 之间的双向绑定模式。

      埃里克,你也可以这样做

         public class MyFloorCollection : BindingList<Floor>
                  {
                      public MyFloorCollection()
                          : base()
                      {
                          this.ListChanged += new ListChangedEventHandler(MyFloorCollection_ListChanged);
      
                      }
      
                      void MyFloorCollection_ListChanged(object sender, ListChangedEventArgs e)
                      {
      
                       if (e.ListChangedType == ListChangedType.ItemAdded)
                       {
      
                          Floor newFloor = this[e.NewIndex] as Floor;
      
                          if (newFloor != null)
                          {
                              newFloor.HeightChanged += new Floor.HeightChangedEventHandler(newFloor_HeightChanged);
                          }
                        }
      
                      }
      
                      void newFloor_HeightChanged(int newValue, int oldValue)
                      {
                          //recaluclate
                      }
      
      
                  }
      

      当然,您可以创建自己的 HeightChangedEvent 并订阅它,这样您就不必在 if 语句中使用属性名称。

      所以你的 Floor 类看起来像这样

       public class Floor : INotifyPropertyChanged
              {
                  private int _height;
      
                  public int Height
                  {
                      get { return _height; }
                      set 
                      {
                          if (HeightChanged != null)
                              HeightChanged(value, _height);
      
                          _height = value;
                          OnPropertyChanged("Height");
      
                      }
                  }
      
      
      
      
                  public int Elevation { get; set; }
      
                  private void OnPropertyChanged(string property)
                  {
                      if (this.PropertyChanged != null)
                          this.PropertyChanged(this, new PropertyChangedEventArgs(property));
                  }
      
                  #region INotifyPropertyChanged Members
      
                  public event PropertyChangedEventHandler PropertyChanged;
      
                  #endregion
      
                  public delegate void HeightChangedEventHandler(int newValue, int oldValue);
                  public event HeightChangedEventHandler HeightChanged;
              }
      

      这样您只需订阅您的 HeightChanged 变量,而不是 PropertyChanged。 DataGridView 将使用 PropertyChanged 来保持 TwoWay 绑定。我相信这种方式更干净。

      您还可以更改委托并将项目作为发件人传递。

      public delegate void HeightChangedEventHandler(Floor sender, int newValue, int oldValue);
      

      编辑:要取消订阅 HeightChanged 事件,您需要覆盖 RemoveItem

        protected override void RemoveItem(int index)
              {
                  if (index > -1)
                      this[index].HeightChanged -= newFloor_HeightChanged;
      
                  base.RemoveItem(index);
              }
      

      【讨论】:

      • 但是 IBindingList 不是通用的,我宁愿不必从 Object 类型转换为我的 Floor 类型。
      • 嗯好吧,这是有道理的。所以你让班级响应它自己的 ListChanged 事件。但是,如果您要从列表中删除楼层,则 Event 关系不会保留,并且会阻止 Floor 被处置,假设事件关系是对该楼层的唯一引用。当列表中的项目发生变化时,也应该触发集合的 ListChanged 事件。我需要手动将 Floor.PropertyChanged 事件链接到 FloorList.ListChanged 事件还是 BindingList 自动执行此操作?
      • Eric:请参阅我从列表中删除 Floor 的答案。它很脏,但你明白了。至于PropertyChanged,我很肯定只要你实现了INotifyPropertyChanged 接口,DataGridView 会自动订阅它并在需要时取消订阅。您可以看到这一点,当您添加新楼层时,请介入并查看 PropertyChanged..您会看到它不为空。
      • 删除项目时,出于调试目的对其进行引用 -- Floor item = this[index];在 RemoveItem 中,然后查看 base.RemoveItem(index) 之后的项目,您会看到 PropertyChanged 为 null 以及 HeightChanged。这样做只是为了查看事件正在被取消订阅,并在您满意后删除该行,因为您不需要该引用;)
      • 嘿,如果这对您有帮助,请告诉我。我很好奇。
      【解决方案3】:

      您可以尝试在某些包含类的数据上公开ObservableCollection&lt;Floor&gt; 属性。此外,Floor 对象需要实现 INotifyPropertyChanged 以支持来自 UI 的双向绑定。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-24
        • 1970-01-01
        • 1970-01-01
        • 2011-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多