In my previous posts I have spoken about how databinding works in Whidbey and some simple scenarios of binding in Whidbey using BindingSource and BindingList<T>. Now, let’s take a closer look at the BindingSource.
BindingSource can be bound to any of the following:
Please note that the ListChanged event on the BindingSource is the main event and will be referenced below. Binding to each of these objects mentioned above has a subtle difference. Let’s go through each of these:
Binding to an Object:
You can arbitrarily set the DataSource of BindingSource to any System.Object. When this is done, BindingSource internally creates a BindingList<T> of that Object type and adds the item to this list. You can then add objects to this list by calling BindingSource.Add(…).
e.g. Say you have a Customer object. You can do the following:
BindingSource bs = new BindingSource();
bs.DataSource = new Customer();
This will create a BindingList<Customer> internally with one object.
Binding to a System.Type:
BindingSource can be bound to a System.Type. When you do this, the BindingSource creates an empty BinidngList<T> of that Type. When you add new items to the BindingSource, it makes sure it is of the correct Type, and if so the item is added, else it will throw. The cool thing is that you can very easily bind any business object this way.
e.g. Say you want to bind to a list of Controls (TextBox, CheckBox, Button, etc). If you set the DataSource of the BindingSource to an instance of a control then it will not work correctly. Consider you do this:
bs.DataSource = new TextBox(); // BindingSource will create a BinidngList<TextBox> internally
bs.Add(new Button()); // This will throw since the type is not TextBox
The correct way to do this would be to set the DataSource to type of control and then add items:
bs.DataSource = typeof(Control); // BindingSource creates a BindingList<Control> internally
This will add the TextBox and Button to the list correctly.
Binding to IEnumerable and ICollection:
When DataSource of BidningSource is set to IEnumerable or ICollection the BindingSource creates a BindingList internally. What this means is that the BindingSource creates a copy of the list and uses it; so it is not aware of any changes you make to the original list.
bs.DataSource = MyCollection; // BindingSource create BindingList internally
BindingSource does not fire ListChanged events in this case since it is unaware of any changes to the underlying list. Also, any changes to the items in the list itself (e.g. if this is a collection of Customers, and if you change say the Name property of a Customer object in the collection), BindingSource will be unaware of these changes since it only references it’s copy. The way to make the BindingSource “aware” of these changes is to set the DataSource of BindingSource again.
bs.DataSource = MyCollection;
Will refresh the BindingList that the BindingSource uses internally.
Binding to IList and IListSource
When DataSource of BindingSource is set to IList or IListSource, BindingSource does not create its internal copy. It will always reference the underlying list for Data. However since IList and IListSource does not have ListChanged events, BindingSource will not be aware when items are added or removed from the list.
Typical case is Binding the BindingSource to ArrayList or List<T> (IList) and DataTable (IListSource). One thing to note though, is that DataTable’s IList.GetList() method returns a DataView which is an IBindingListView and not a pure IList. That’s why DataTable would behave differently from an IListSource that returns a pure IList instead. More information on IBindingListView below.
List<Customer> list = new List<Customer>();
// Fill the list
bs.DataSource = list;
DataGridView dgv = new DataGridView();
dgv.DataSource = bs;
This will bind DGV to BindingSource which is bound to List<T>.
list.Add(new Customer()); // Add a new Customer to the list
BindingSource is not aware of this change and so does not fire ListChanged event. Thus, DGV does not show a new row. If you refresh the DGV then it will get the data from the BindingSource which will get it from the underlying list and then the new row will appear.
To show the changes of items added and removed to the list, you can use BindingList<T> instead. Details below.
Binding to IBindingList and IBindingListView
When DataSource of BindingSource is set to IBindingList or IBindingListView, the BindingSource does not create internal copy of the list (just as it does in the case of IList and IListSource). It always references the underlying list for the Data. Also, since IBindingList and IBindingListView have the ListChanged event, the BindingSource hooks on to those events and so it is aware of any items added or removed from the list.
BindingList<Customer> bList = new BindingList<Customer>();
bs.DataSource = bList;
bList.Add(new Customer()); // Add a new Customer to the list
BindingList<T> fires ListChanged event when a new item is added to the list. Since BindingSource hooks on to the ListChanged event of BindingList<T> it also is “aware” of these changes and so the BindingSource fires the ListChanged event. This will cause the new row to appear instantly in the DataGridView or make any controls listening to the BindingSource “aware” about this change.
Next I will talk about making changes to properties of an item in the list (e.g. changing the Name property of a Customer), rather than changing the list by adding and removing items, and we can take a closer look at how the BindingSource deals with these changes.