简体   繁体   English

BindingList 与 List - WinForms 数据绑定

[英]BindingList vs List - WinForms Data Binding

In order to get data binding in WinForms (to a DataGridView, for instance) to work anything like you'd hope and add/delete rows as the collection changes, you have to use a BindingList (or DataTable) instead of a generic List.为了让 WinForms 中的数据绑定(例如到 DataGridView)像您希望的那样工作并在集合更改时添加/删除行,您必须使用 BindingList(或 DataTable)而不是通用列表。 The problem is, almost nobody has the first instinct to code with a BindingList instead of a List in their libraries.问题是,几乎没有人的第一直觉是在他们的库中使用 BindingList 而不是 List 进行编码。

The BindingList implements two events that the List doesn't have and these must be the difference in data binding action (also, a property to suppress the second event): BindingList 实现了两个 List 没有的事件,它们必须是数据绑定操作的不同之处(也是抑制第二个事件的属性):

AddingNew
ListChanged
RaiseListChangedEvents

Similarly, the DataTable has two events which probably enable similar functionality:同样,DataTable 有两个事件可能会启用类似的功能:

RowDeleted
TableNewRow

EDIT : As the helpful SO community pointed out here and in another article , a List can be converted (maybe more accurately encapsulated?) by calling the correct BindingList constructor:编辑:正如有用的 SO 社区在此处和另一篇文章中指出的那样,可以通过调用正确的 BindingList 构造函数来转换 List(也许更准确地封装?):

BindingList<MyType> MyBL = new BindingList<MyType>();
MyList.ForEach(x => MyBL.Add(x));

My situation is a little more complicated as illustrated by the code below.我的情况有点复杂,如下面的代码所示。 EDIT Added INotifyPropertyChanged stuff that must exist in the real library.编辑添加了必须存在于真实库中的 INotifyPropertyChanged 内容。

public class RealString : INotifyPropertyChanged
{
  private int _KnotCount = 0;
  private List<KnotSpace> _KnotSpacings = new List<KnotSpace>();

  public RealString()
  {
    KnotSpacings.Add(new KnotSpace());
  }

  public int KnotCount
  {
    get { return _KnotCount; }

    set
    {
      int requiredSpacings = 0;

      _KnotCount = value;
      // Always one more space than knots
      requiredSpacings = _KnotCount + 1;

      if (requiredSpacings < KnotSpacings.Count)
      {
        while (requiredSpacings < KnotSpacings.Count)
        {
          KnotSpacings.Add(new KnotSpace());
        }
      }
      else if (requiredSpacings > KnotSpacings.Count)
      {
        while (requiredSpacings > KnotSpacings.Count)
        {
          KnotSpacings.Remove(KnotSpacings.Last());
        }
      }
      this.OnPropertyChanged(this, "KnotCount");
    }

  }

  public List<KnotSpace> KnotSpacings { get => _KnotSpacings; }

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged(object sender, string PropertyName)
  {
    if (this.PropertyChanged == null) return;
    this.PropertyChanged(sender, new PropertyChangedEventArgs(PropertyName));
  }

}

public class KnotSpace
{
  private double _Spacing = 10;

  public double Spacing { get => _Spacing; set => _Spacing = value; }

}

The things in the list are displayed in the UI, and the properties of the things in the list are modified in the UI, but the UI doesn't directly add/remove things from the list except by changing the KnotCount property.列表中的事物显示在 UI 中,列表中事物的属性在 UI 中进行修改,但 UI 不会直接从列表中添加/删除事物,除非更改 KnotCount 属性。 Wrapping the KnotSpacings property in a BindingList doesn't result in the BindingList updating when KnotSpacings is updated by changing the KnotCount property.当通过更改 KnotCount 属性更新 KnotSpacings 时,将 KnotSpacings 属性包装在 BindingList 中不会导致 BindingList 更新。

EDIT OK, more clarification...编辑好的,更多说明......

BindingList BL = new BindingList<KnotSpace>(MyRealString.KnotSpacings);

DataGridView1.AutoGenerateColumns = true;
DataGridView1.DataSource = BL;
NumericUpDown1.DataBindings.Add("Value", MyRealString, "KnotCount", false, DataSourceUpdateMode.OnPropertyChanged);

The BindingList has no more success tracking the changes to the underlying List property (KnotSpacings) than the Windows controls.与 Windows 控件相比,BindingList 在跟踪基础 List 属性 (KnotSpacings) 的更改方面没有更多成功。 So data binding the controls to the BindingList doesn't accomplish much.因此,将控件绑定到 BindingList 的数据并没有太大的作用。 BindingList works great if UI adds/removes items from the BindingList because it does the same operations in the underlying List.如果 UI 从 BindingList 添加/删除项目,则 BindingList 效果很好,因为它在底层 List 中执行相同的操作。 But then I would need to replicate the add/remove action and logic of the library in my UI and that's a breaking change in waiting.但随后我需要在我的 UI 中复制库的添加/删除操作和逻辑,这是等待中的重大变化。

EDIT Major changes made to my original post attempting to: (1) Clarify the problem.编辑对我的原始帖子所做的主要更改,试图:(1)澄清问题。 (2) Distinguish it as not a duplicate question (although one of the several questions was a dup). (2) 将其区分为不是重复问题(尽管几个问题之一是重复问题)。 (3) Acknowledge the helpful efforts of others that would be lost if I deleted the post. (3) 承认如果我删除帖子可能会失去其他人的有益努力。

First Off, there is a better way to pass a List<T> to a BindingList<T> .首先,有一种更好的方法可以将List<T>传递给BindingList<T> BindingList<T> has a constructor that accepts a List<T> which copies the List 's elements into the BindingList , like so: BindingList<T>有一个接受List<T>的构造函数,它将List的元素复制到BindingList ,如下所示:

List<int> myList = new List<int>();
BindingList<int> myBindingList = new BindingList<int>(myList);

But that's not your question, really.但这不是你的问题,真的。 To answer your question simply - Correct, List<T> is not a good choice for two-way binding in WinForms .简单地回答您的问题 -正确, List<T>不是 WinForms 中双向绑定的好选择 As List<T> does not have any events notifying for elements added, you can really only guarantee a one-way binding - data entry may work, but things break down when trying to refresh on, say, items being added to the List .由于List<T>没有任何事件通知添加的元素,因此您实际上只能保证单向绑定 - 数据输入可能有效,但在尝试刷新时,例如添加到List项目时,事情会崩溃。

That said, you mention that these libraries are modifying a List<T> that you have access to during the modifications.也就是说,您提到这些库正在修改您在修改期间有权访问的List<T> I would argue that a good Library would use the Interface pattern to use, modify, and pass collections.我认为一个好的库会使用接口模式来使用、修改和传递集合。 Although List<T> and BindingList<T> are very different classes, they both implement IList<T> , ICollection<T> , and IEnumerable<T> .尽管List<T>BindingList<T>是非常不同的类,但它们都实现了IList<T>ICollection<T>IEnumerable<T> So any function which accepts any of those interfaces as a parameter would accept either a List<T> or a BindingList<T> (for example: public void DoSomethingWithCollection(IEnumerable<int> collection) could accept List<int> , BindingList<int> , or any other collection that implements IEnumerable<int> ).因此,任何接受这些接口作为参数的函数都可以接受List<T>BindingList<T> (例如: public void DoSomethingWithCollection(IEnumerable<int> collection)可以接受List<int> , BindingList<int> ,或任何其他实现IEnumerable<int>集合)。 The Interface pattern is a well-known standard at this point in C#'s lifespan, and though nobody's first instinct would be to use a BindingList<T> over a List<T> , their first instinct should absolutely be to use an IEnumerable<T> (or IList<T> or ICollection<T> ) over a List<T> . Interface 模式是 C# 生命周期中的一个众所周知的标准,尽管没有人的第一直觉是在List<T>上使用BindingList<T> List<T> ,但他们的第一直觉绝对应该是使用IEnumerable<T> (或IList<T>ICollection<T> )在List<T>

Where possible, it would be better for binding to pass your List to the BindingList 's constructor, then never use the List again - instead, use the Add and Remove methods of the BindingList to manage it's internal collection.在可能的情况下,绑定将您的List传递给BindingList的构造函数会更好,然后永远不要再次使用List - 相反,使用BindingListAddRemove方法来管理它的内部集合。

If you use the BindingList<T> constructor that accepts an instance of IList<T> , then that instance is used to back the BindingList<T> , and changes in the IList<T> are reflected in the BindingList .如果您使用接受IList<T>实例的BindingList<T>构造函数,则该实例用于支持BindingList<T> ,并且IList<T>中的更改反映在BindingList

That's not the end of the story, however.然而,这并不是故事的结局。 WinForms databinding is structured in such a way that, the further away you get from simple, single-property 2-way binding, the more things you have to cover yourself. WinForms 数据绑定的结构方式是,您离简单的、单一属性的 2 向绑定越远,您需要涵盖的内容就越多。

For example, the INotifyPropertyChanged interface is implemented by classes that are used as a data source to notify of a change in a child property (like your KnotCount property).例如, INotifyPropertyChanged接口由用作数据源的类实现,以通知子属性(如KnotCount属性)的更改。

For more complex scenarios, one would not use BindingList<T> , but would derive a class from it and override one or more of the data binding mechanisms.对于更复杂的场景,我们不会使用BindingList<T> ,而是从它派生一个类并覆盖一个或多个数据绑定机制。 Ditto for the BindingSource class. BindingSource类也是如此。

There is a lot of boilerplate behind the data binding mechanism, but almost every portion of it is open to derivation in order to customize the behavior.数据绑定机制背后有很多样板,但几乎每个部分都可以派生以自定义行为。 It is sometimes useful to draw out an object graph of the classes and interfaces used in data binding (lots of reading the documentation involved) to give yourself a good mental overview of the whole process.绘制数据绑定中使用的类和接口的对象图有时很有用(大量阅读相关文档),以便对整个过程有一个很好的心理概述。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM