简体   繁体   中英

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. The problem is, almost nobody has the first instinct to code with a BindingList instead of a List in their libraries.

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):

AddingNew
ListChanged
RaiseListChangedEvents

Similarly, the DataTable has two events which probably enable similar functionality:

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:

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.

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. Wrapping the KnotSpacings property in a BindingList doesn't result in the BindingList updating when KnotSpacings is updated by changing the KnotCount property.

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. So data binding the controls to the BindingList doesn't accomplish much. BindingList works great if UI adds/removes items from the BindingList because it does the same operations in the underlying 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.

EDIT Major changes made to my original post attempting to: (1) Clarify the problem. (2) Distinguish it as not a duplicate question (although one of the several questions was a dup). (3) Acknowledge the helpful efforts of others that would be lost if I deleted the post.

First Off, there is a better way to pass a List<T> to a BindingList<T> . BindingList<T> has a constructor that accepts a List<T> which copies the List 's elements into the BindingList , like so:

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 . 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 .

That said, you mention that these libraries are modifying a List<T> that you have access to during the modifications. 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> . 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> ). 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> .

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.

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 .

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.

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).

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. Ditto for the BindingSource class.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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