Data Binding Components
The two key classes that simplify complex binding scenarios in Windows Forms 2.0 are the BindingSource component and the Binding class. The BindingSource component is used for setting up associations between controls and collections of data, and the Binding class is used to set up a one-to-one correspondence between an individual property on a control, and a data property or field within a data source. These objects basically provide a layer of indirection between the bound controls and their actual data source. Instead of binding the controls directly to the data sources or collections of data objects, you instead bind the controls to either a BindingSource object or a Binding object.
|Instead of binding the controls directly to the data sources or collections of data objects, you instead bind the controls to either a BindingSource object or a Binding object.|
You use a BindingSource if the control displays information from more than one data item in the collection at a time, such as a grid, list, or combo box, and you use a Binding object if the control only displays information from a single property or field on a single data item at a time, such as a label or textbox. Binding objects can use a BindingSource as their data source, and one BindingSource can be used as the data source for another BindingSource, allowing you set up hierarchical bindings.
Figure 2 depicts various combinations of form controls and binding objects. The arrows represent a data source reference from one object to another. You can see the use of a BindingSource as an intermediary between a collection (data source) and a grid or list control. You can also see that I used one BindingSource as the data source for another BindingSource, where the first BindingSource is set as the data source for a ListBox in the form. TextBox controls will always use a Binding object to set up the data binding, but that Binding object will often use a BindingSource as its data source, providing a layer of abstraction between the control bindings and the underlying data.
|Figure 2: Controls, binding objects, and data sources.|
There are several advantages to having this extra layer of abstraction. The first is that if multiple controls are bound to the same data source, they can all be updated to a new or refreshed data source by setting the data source on the BindingSource, rather than updating the data source on every individual control. Additionally, the BindingSource object gives you direct access to the underlying collection of data as a list of objects, allowing you to work with the collection without knowing its type. The BindingSource component also gives you easy access to the current item within the collection, the ability to set which item is current, and it raises a number of events that allow you to monitor changes in the underlying data source in ways that were very challenging to achieve prior to the introduction of the BindingSource component.
To work with a BindingSource component, you set its DataSource property to reference a collection of data items, where the collection implements the IList interface. If the data source is something like a DataSet, which contains a collection of collections, then you can set the DataMember property of the BindingSource to the name of the property within the data source that represents the list that you want to bind to. To work with a Binding object, you create an instance of the Binding object and add it to the DataBindings collection on the Control base class from which all controls derive. In creating the Binding object, you specify the name of the property on the control that is being bound (such as the Text property on a TextBox control), the data source (which can be a collection or an individual object instance), and the name of the data member within the data source to bind to the control property (such as the au_name field if the data source represents the authors table).
Now that you have some data to work with and you have a sense of what the BindingSource and Binding objects are, let's set up some binding scenarios to make the concepts more concrete and demonstrate how to use them. One of the most common forms of complex data binding is to set up master-details binding. Master-details refers to when you have two collections of data, such as titles and publishers in the pubs database, where there is a parent-child relationship between items in one collection and items in the other. In the case of titles, each title has a publisher, identified by the pub_id column in the table. Using that foreign key value, you can locate the corresponding row in the publishers table for display purposes. This is basically a way of displaying a one-to-many relationship between data items.
If you were dealing with custom objects in a collection instead of using a DataSet, each parent object in the collection of parent objects would have to expose a public property that refers to a collection of child objects. If that collection implements the IList interface, you can then set up master-details data binding with the custom objects in the same fashion as with two data tables in a DataSet related by a data relation.
When you set up master-details binding, you typically show a grid or list control with the parent items in it (publishers), and when a given parent item is selected, you display all of the related child items (titles) in another grid or list control. In the sample application in the download code, there is a form that has two DataGridView controls on it, one for publishers and one for titles. There are two BindingSources contained by the form as well, one for publishers and one for titles, and they are each set as the DataSource for their respective grids. The publishers BindingSource has its DataSource property set to the publishers table of the PubsDataSet typed DataSet that gets populated by the data access layer class. The titles BindingSource has its DataSource set to the BindingSource for the publishers, with its DataMember property set to the data relation on the table that links the publishers and titles foreign key relation (FK_titles_publishers). Listing 1 shows the code that hooks this all up.
The form load handler for the master-details form first populates the PubsDataSet instance in the form using the data access class method shown earlier. It then sets the data source for each of the grids to their respective BindingSource objects. Then the DataSource and DataMember properties for each of the BindingSource objects is initialized. For the publishers parent data, the BindingSource DataSource is set to the DataSet and the DataMember is set to the table name of the publishers table. Note that I chose to use the strongly typed properties of the DataSet to specify the table name in a way to avoid hard coding schema object names in my code. This way if the table name changes in the future, the code will fail to compile on the line that sets the data member, making it easier to correct problems caused by schema changes.
For the child table BindingSource, I set the DataSource to the parent BindingSource object (m_PublishersBindingSource), and specify the DataMember as the name of the data relation in that parent DataSet that relates the child table to the parent table (FK_titles_publishers). Unfortunately there is no strongly typed way to specify the relation.
By chaining BindingSources with one BindingSource's DataSource property and setting it to reference another BindingSource and setting its DataMember property to the name of a child collection property within the parent BindingSource's data items, you can have arbitrarily deep nesting of hierarchical data sources. The Windows Forms synchronization mechanisms, in conjunction with the BindingSource objects, will take care of keeping them all synchronized based on selections within controls that are bound to them.