BindingList<T> allows us to use almost any class to create a data-binding-savvy strongly typed list data source. However, some item classes come already associated with their own collection classes. Although any collection class that implements IList can be used as a list data source, you don't get full-flavor data binding if you don't implement IBindingListnamely, support for two-way list and item change notification. To gain this support and to avoid the highly involved implementation of IBindingList ourselves, we'd love to be able to "upgrade" an existing IList implementation to IBindingList. The class that performs this upgrade for you is BindingSource. The BindingSource component (from System.Windows.Forms) consumes either item types or list types and exposes them as IBindingList implementations. As you'll see throughout this chapter and well into the next, BindingSource gives you an enormous amount of data binding ability, both in code and via designers. All this ability hinges on BindingSource acting as an IBindingList implementation for types that don't implement IBindingList themselves. Turning an Item Type into a List Data SourceTo turn a less-than-IBindingList type into an IBindingList type, you might consider trying something like this: // BindingSourceForm.cs partial class BindingSourceForm : Form { // Create strongly typed list data source List<RaceCarDriver> raceCarDrivers = new List<RaceCarDriver>(); // Create a BindingSource BindingSource raceCarDriversBS = new BindingSource(); public BindingSourceForm() { InitializeComponent(); // Populate list data source with data items this.raceCarDrivers.Add(new RaceCarDriver("M. Schumacher", 500)); this.raceCarDrivers.Add(new RaceCarDriver("A. Senna", 1000)); this.raceCarDrivers.Add(new RaceCarDriver("A. Prost", 400)); // This code augments the List<RaceCarDriver> with IBindingList // list management and change notification capabilities // but only when used via the BindingSource this.raceCarDriversBS.DataSource = this.raceCarDrivers; ... } ... } This code augments a List<T> instance with IBindingList list management and change notification support via the BindingSource. After the binding is established, you programmatically add and delete items via BindingSource for the IBindingList list support to work, because that's where the list change notification logic is located: // Add items to binding source - GOOD this.raceCarDriversBS.Add(new RaceCarDriver("M. Schumacher", 500)); If you programmatically add items via the original list, such as our raceCarDrivers object, change notification does not work: // Add items to list data source directly - BAD this.raceCarDrivers.Add(new RaceCarDriver("M. Schumacher", 500)); To avoid providing two unequal APIs to your list dataone with data change notifications and one withoutyou can turn over storage responsibilities completely to BindingSource. To specify that BindingSource should be responsible for the list-based storage of your item data type, you seed the DataSource property with the type of the item data type: // BindingSourceForm.cs partial class BindingSourceForm : Form { ... // Create a BindingSource BindingSource raceCarDriversBS = new BindingSource(); public BindingSourceForm() { InitializeComponent(); ... // This will allow BindingSource to act as a list data source // that operates over the RaceCarDriver item this.raceCarDriversBS.DataSource = typeof(RaceCarDriver); ... } ... } Internally, BindingSource instantiates BindingList<T> to operate over the designated type and provide IBindingList services. The result is that BindingSource now both provides and manages the storage of a list data source that operates over the desired type. To access the storage provided by BindingSource, you use the List property, which is of type IList: IList bindingSourceStorage = this.raceCarDriversBS.List; In addition to providing storage based on the type of the item data, you get the same behavior from BindingSource if you provide a list data source of a type that doesn't implement IBindingList: // BindingSourceForm.cs partial class BindingSourceForm : Form { ... // Create a BindingSource BindingSource raceCarDriversBS = new BindingSource(); public BindingSourceForm() { InitializeComponent(); ... // This will allow BindingSource to act as a list data source // that operates over the RaceCarDriver item this.raceCarDriversBS.DataSource = typeof(RaceCarDriver); ... } ... } In this case, BindingSource inspects the list type and extracts the item type over which it operates, using BindingsList<T> for storage as before. Using BindingSource at Design TimeThe act of using either an item or a list data type to indicate storage requirements to the BindingSource is known as shaping. The idea is that the type of the item provides the shape of the data being bound to, and this shape is used at design time to provide a list of item data properties that support binding for any particular type. Shaping is combined with component-based interaction to provide a consistent design-time binding experience no matter what kind of data you're binding to. This allows BindingSource to project any data source onto a form's design surface and, even better, allows controls to bind directly against the BindingSource itself, thereby allowing you to shape a data-bound UI against any data source, whether or not that data source is a component (most data sources aren't) and whether or not that data source implements IBindingList (most data sources don't). To expose any CLR object or list as a data source, you drag a BindingSource from the Data tab of the Toolbox and drop it onto a design surface, as shown in Figure 16.14. Figure 16.14. The BindingSource Design-Time Component
When on the design surface, a BindingSource component opens a Pandora's Box of design-time fun for declaratively configuring bound UIs. |