Many developers have a dream: easy and efficient data binding.

To be really quick and profitable, RAD (rapid application development) tools and techniques must be strong in data binding. They must provide a programming interface that is both easy to use and effective. Easy design-time composition of user interfaces; effective support of complex scenarios of interrelated data, dependencies, and filtering. In Windows Forms, the data binding machinery is highly sophisticated and designed to meet common needs of former client/server applications, now migrating to the more modern .NET multi-tier design. This article reviews common Windows Forms data binding techniques and provides answers and explanations.

Whenever you set the DataSource property of a ComboBox control, the control retrieves and updates its binding context to reflect the new data source.

Like hunters and fishermen, developers always have stories to tell. Most times these stories are spiced up with some emphasis and include unnecessary details that are not necessarily true. I have stories that I tell to show how I brilliantly solved hundreds of tough problems for the uncontrolled happiness of paying clients. The story I'm going to summarize here is a bit different. There's no critical problem to work around to save a valuable deadline, and there's no architectural or design issue behind it. It's a simple story of a mere trick for developers?it's not even particularly neat and elegant. However, about three years ago it triggered a process that made me appreciate and take much more advantage of the Windows Forms data binding architecture.

Synchronized Controls

A reader sent me the following message. "I have a Windows form with two ComboBoxes that I want to show exactly the same data. When I select an element on the former, the latter automatically reflects the selection. And vice versa. I'm desperate. Please help."

I replied promptly to ask why on Earth two absolutely identical ComboBoxes were required on the same form. It turns out the form was intended to collect user details including a primary and secondary address. The ComboBox was therefore used to list available zip codes.

It took me a few minutes to reproduce the problem. I created a new Visual Studio .NET project, dropped in a couple of ComboBox controls and created a data source. Next I bound the DataSource properties of the comboBoxes to the same data source object and did the same with display and value fields. The code snippet below illustrates the point.

cboEmp1.DataSource = m_dataSet;
cboEmp1.DisplayMember = "Employees.lastname";
cboEmp1.ValueMember = "Employees.employeeid";

cboEmp2.DataSource = m_dataSet;
cboEmp2.DisplayMember = "Employees.lastname";
cboEmp2.ValueMember = "Employees.employeeid";

The sample application worked like a champ?that is, in this context, bad. The two controls worked like an invisible hand had synched them up. If there were a hand, though, it was not mine. I tried an equivalent piece of code, shown below.

cboEmp1.DataSource = m_dataSet
cboEmp1.DisplayMember = "Employees.lastname"
cboEmp1.ValueMember = "Employees.employeeid"

cboEmp2.DataSource = m_dataSet.Tables("Employees")
cboEmp2.DisplayMember = "lastname"
cboEmp2.ValueMember = "employeeid"

Surprisingly this worked and each ComboBox control retained its own selection completely unaffected by the other's behavior. Happy with the trick, I got back to the reader and presented the workaround as the complete solution to the issue. The reply caused me to pause: What if I have three ComboBoxes?

The Binding Context

At that point I decided to take the plunge into the intricacies of Windows Forms data binding. The BindingContext property was the first element I selected for further investigation. The property is common to all Windows Forms controls as it is implemented in the base Control class. So what's the binding context of a Windows control?

Technically speaking, the binding context is a collection of objects, as the code below demonstrates.

public class BindingContext :
       ICollection, IEnumerable

Whenever you set the DataSource property of a data-bound control (i.e., a ComboBox control), the control retrieves and updates its binding context to reflect the new data source. Each control has its own unique binding context. If no context is explicitly assigned, the control inherits the binding context of the container where the control is hosted?typically the form or a panel control.

What kind of objects are stored in the binding context? The binding context contains binding managers. A binding manager exists for each distinct data source used by data-bound controls in the container. For example, if you a have a DataGrid and a ComboBox control on a form, each bound to a different data source object, the binding context of the form will contain two entries?one for each data source. If two or more controls in the form share the same data source object, a single binding manager will be created in the binding context. The two preceding code snippets differ just on the number of binding managers created in the binding context. The two ComboBoxes work in sync when they share the same data source object and subsequently the same binding manager. They work independently from each other when different data source objects are employed. In both cases, and this is the key point, they use the same binding context collection.

A binding manager is an instance of a class that inherits from BindingManagerBase. Figure 1 provides a graphical representation of binding context and binding managers.

Figure 1: Each data-bound control in the form references a binding context collection. The collection, in turn, contains references to binding managers.

Two classes in the .NET Framework inherit from BindingManagerBase: CurrencyManager and PropertyManager. To understand their roles and differences, let's first tackle the behavior of a binding manager. You get a reference to a binding manager through the following code:

BindingManagerBase bmb;
bmb = Form1.BindingContext[m_dataSet, "Orders"];

The binding manager in the code snippet manages all controls bound to the Orders table of the specified DataSet. These controls share the information stuffed into the internal members of the specific binding manager type. What this information is, and refers to, depends on the binding manager type.

Binding Managers

Binding managers provide a central repository of information for data-bound controls on a Windows Form that are bound to the same data source. In doing so, binding managers enable the synchronization of related controls.

You can handle events (Format and Parse) to control how the data is marshaled back and forth between the user interface and the back end memory.

Clients of distributed applications (in most cases, just evolutions of former client/server applications) need to populate rich and sophisticated user interfaces. Typically, they require a lot of work to refresh controls when the selection changes or when a user chooses a different view of data.

Binding managers form the infrastructure of an innovative architecture based on auto-update controls that work in sync and detect changes on the memory objects they're bound to.

For example, suppose you have two DataGrids that form a master/detail view. In a non-Windows Forms scenario, you have to assign each grid an explicit role?be it master or detail. On the master grid you hook up the SelectionChanged event and invoke a method on the child grid passing some information like the ID of the selected record. In turn, the child grid uses that information to refresh its view and stay in sync with the master grid.

There's coupling between the two grids; but, more important than that, you have to set any required logical relationship in code.

What if you have several relationships to manage and a cascade of nested tables and views? In this case, the quantity of synchronization and glue code grows easily beyond the reasonable threshold of manageability. Testing and maintaining this kind of code is extremely difficult.

Binding managers, instead, provide a common interface to data-bound controls and enable them to implicitly handle events and query for required data. Data-bound controls are therefore smarter and require much less code to set up even sophisticated clients. In simple cases you can even set up two or more levels of details view in a declarative manner.

Central to the design of the Windows Forms data binding mechanism is the ADO.NET DataSet object. You bind all controls to the same object reference?the DataSet?and give each control a different navigation path to make it handle a different subset of data. The DataSet is a collection of tables and relations; the navigation path uses both table and relation names to express, in a custom syntax, the subset of rows the control will display.

CurrencyManager Class

The CurrencyManager manages a collection of bindings between list controls and a data source object. It exposes ad hoc members to let controls retrieve the currently selected item (the Current property), get and set the position of the current item, and be notified of position or current item changes via the PositionChanged and CurrentChanged events. Table 1 lists the properties of the class.

The CurrencyManager class standardizes the access to the variety of enumerable lists you can use in .NET data-bound applications. Whenever a list control (such as a ComboBox) changes its selected item because the user clicks on a row, the Current and Position properties on the associated binding manager object are updated. When this happens two events fire: CurrentChanged and PositionChanged.

Both events are served by an EventHandler delegate object and don't carry specific information to the caller. Both events are notifications that something happened in the data source. All controls designed to work in master/details scenarios can hook up these two events and automatically refresh. This is the underlying mechanism that explains the otherwise weird phenomenon of two ComboBoxes working unexpectedly (and in a manner not required to some extent) in sync.

In light of this information, let's briefly review the internal steps that bring one ComboBox in a form to update its selection whenever another similar control, bound to the same data source, changes its own selection.

Suppose you have two ComboBox controls in a Windows Forms and you bind both to the same data source object?say, a DataSet. When the DataSource property of the first ComboBox is set, a new entry is created in the BindingContext collection of the form to link together a new instance of the CurrencyManager class and the data source. At the same time, the Bindings collection of the CurrencyManager adds a reference to the ComboBox control.

When the second ComboBox is bound to the data source, the just-created binding manager is retrieved from the form's context and its Bindings collection is updated to take the second ComboBox into account.

After initialization each ComboBox control also adds a handler for the CurrentChanged event on the binding manager. The control retrieves its binding manager using the following code:

// m_dataSource is the internal member
// storing the data source object
CurrencyManager cm;
cm = BindingContext[m_dataSource];

The following pseudo-code shows how the control defines its event handler for the CurrentChanged event.

cm.CurrentChanged +=
         new EventHandler(SelectionChanged);

The SelectionChanged event handler in the preceding code is a ComboBox internal function called to react to the change of selection that takes place in any control in the context bound to the same data source.

Because of the way the event handlers are attached to a ComboBox when a new item is selected (either at startup or after a user's action), the Current and Position properties on the binding manager update to reflect the change. Then the binding manager fires the related events, namely the CurrentChanged and PositionChanged events. All controls in the binding context that subscribed to these events automatically receive the notification.

Now imagine you have a form with two ComboBox controls bound to the same data source. When one control changes the selected item, the other receives a notification from the currency manager and invokes its own selection-change event handler. In the case of ComboBox controls, the selection handler simply selects the same element that is current on the data source. The following pseudo-code illustrates the point.

void SelectionChanged(object sender,
                      ItemChangedEventArgs e)
{
   if (bindingManager == null)
      return;
  
   // Position points to the index of the newly
   // selected item
   this.SelectedIndex = bindingManager.Position;
   :
}

If you love decompilers and you want to see the real source code of this feature, then locate the DataManager_ItemChanged method in the ListControl class in the System.Windows.Forms namespace. ListControl is the base class of ComboBox.

How each control reacts to selection-change style events depends on the characteristics and behavior of the control itself. For example, the DataGrid control may raise an event when a row is changed, whereas a Drop-Down ListBox will raise an event when the user selects a new item. I'll return to this point shortly with an example. Meanwhile, let's look at the other, simpler, type of binding manager class.

The PropertyManager Class

The PropertyManager class maintains a binding between a data source object's property and a data-bound control property. It differs from CurrencyManager in that it doesn't manage a list of values but it does manage single property values.

Table 2 lists the methods defined on the base binding manager class and overridden by derived classes.

The SuspendBinding and ResumeBinding methods allow the temporary suspension and resumption of data binding on a given data source. This typically happens when you want to allow the user to make several edits to data fields before validation occurs. In a complex user interface, it is common that you need to change one field in accordance with a second, but changing the first field may cause temporary inconsistency in the second field until you change that too. Because the data binding process is automatic in nature you would get an exception. To avoid that exception, suspend binding and resume it when all values are consistent again.

The PropertyManager gets into the game when you use simple bindings such as when you link a control's property to a data source column?for example, you may want the Text property of a Label to display the LastName column of the Employees table.

Binding b1;
b1 = new Binding("Text", m_dataSet,
                 "Employees.LastName");
lblLastName.DataBindings.Add(b1)

The code above creates a Binding object between a property named Text and the LastName column in the Employees table within the specified DataSet. The Binding object doesn't know anything about controls. It merely represents a logical property-to-column relationship. The binding becomes effective only when you add the Binding object to the DataBindings collection of a live control.

When you add a Binding object to the DataBindings collection of a Windows Forms control, a property binding manager is set up to guarantee that a property with the specified name is given the value of the specified column for the current record. I'll return later to the topic of Binding objects when I show you how to implement data binding with pictures and special data types.

Codeless Master/Detail

The code in Listing 1 creates a form with three DataGrid controls each displaying one of three inter-related tables: employees, orders, and order details. When the user selects an employee, the form updates the order and detail view to show all orders processed by the employee, and all the items included in the first order in the view. The user manually selects the item in the first DataGrid and the other two DataGrids update themselves automatically based on the services of the binding manager.

Let's focus on the following excerpt from Listing 1.

EmployeeGrid.DataSource = m_data
EmployeeGrid.DataMember = "Emp"
OrdersGrid.DataSource = m_data
OrdersGrid.DataMember = "Emp.Emp2Ord"
DetailsGrid.DataSource = m_data
DetailsGrid.DataMember = "Emp.Emp2Ord.Ord2Det"

All three DataGrids are bound to the same data source object?a DataSet object named m_data in the example. The DataMember property of a data-bound control selects a collection of data out of DataSet. In most cases, DataMember points to the name of a child DataTable object. The EmployeeGrid control, for example, displays the contents of the Emp table. A bit more interesting is the expression assigned to the DataMember property of the other two DataGrid controls. The expression is based on a custom syntax?a dot-separated sequence of strings?that data-bound controls can interpret. The value of the DataMember property is known as a navigation path within the data source.

A navigation path resolves to a list or property in the data source. The expression typically includes the table name plus column name or one or more relation names. A relation name is the name of any DataRelation object defined between a pair of DataTables in the DataSet. Let's consider the expression Emp.Emp2Ord, which is the name of a data relation object defined as follows.

Dim col1 As DataColumn
Dim col2 As DataColumn
col1 = m_data.Tables("Emp").Columns("employeeid")
col2 = m_data.Tables("Ord").Columns("employeeid")

Dim rel1 As DataRelation
rel1 = New DataRelation("Emp2Ord", col1, col2)
m_data.Relations.Add(rel1)

The data relation defines a link between the employees and the orders table set on the common employeeid column. When assigned to the DataMember property of a grid, the expression Emp.Emp2Ord indicates that the grid will display all records from the orders table related to the currently selected record on the parent table of the relation?the employees table.

A similar pattern determines the meaning of the expression Emp.Emp2Ord.Ord2Det. In this case, displayed records come from the table with order details and consists of all items in the currently selected order.

When a DataGrid control is bound to a data source, it retrieves the current binding manager for the data source and hooks up the CurrentChanged event. When the current record on the data source changes (no matter which control modified the selection), the DataGrid receives an event. At this point, each DataGrid control by default looks at the navigation path stored in its DataMember property. The first token, if any, is considered as the table name. The next token can either be a column or relation name. If it is a relation name then the grid retrieves all the records in the child table that match the relation. This is accomplished using the GetChildRows method on the DataRow class.

DataRow parentRow = ... ;
DataRow[] childRows;
childRows = parentRow.GetChildRows(relName);

As you can see, GetChildRows returns an array of DataRow objects. The elements in the array are then used to populate the grid. In this way, each DataGrid with a proper navigation path can automatically refresh its contents whenever a selection changes in the logical tree of data. As a margin note, consider that no DataGrid control you may have in the form is aware of the others. Figure 2 shows the form of Listing 1 in action in a sample application.

Figure 2: A master/detail page with three grids.

Any click on a grid to select a record pushes changes to other grids down the hierarchy. Which grid is the master, and which is the slave. is not written anywhere; or, at least, it is not written clearly or in code. The grid's role is wholly determined by the navigation path assigned to the DataMember property of the control. The data to display is extracted from the same data source object?the DataSet class.

What About Custom Controls?

In Windows Forms only a few controls are data-bound and support the data binding mechanism as described so far. They are: ComboBox, ListBox, and DataGrid. Other controls, including TextBox, support a simpler binding model?the model managed by the PropertyManager object. So what about custom controls?

Can you write a new control that behaves like the DataGrid or the ComboBox? Sure you can.

In an article I wrote in the February 2002 issue of MSDN Magazine I discussed a data-bound ListView control that detects selection changes in a ComboBox or DataGrid and refreshes its own view.

In the set accessor of the DataSource property you retrieve the binding manager and hook up the CurrentChanged event. When the event fires you retrieve the DataMember property and interpret its content the way you like. You are free to employ a completely custom syntax to let callers pass information to your control through the DataMember property. The value assigned to DataMember should be anything your control can understand. A dot-separated navigation path is simply the .NET Framework default syntax. As such, it is arbitrary and can be changed in a custom control.

In summary, Windows Forms data-bound controls follow a pattern that automatically synchronizes them when bound to the same data source object. (Note that I'm talking about the same data source object; not just the same data.) This happens because all controls inherit the container's binding context. To keep two or more controls that are bound to the same data source out of sync, just link them to distinct binding contexts. Here's an example:

cboEmp1.BindingContext = New BindingContext
cboEmp1.DataSource = m_dataSet
cboEmp1.DisplayMember = "Employees.lastname"
cboEmp1.ValueMember = "Employees.employeeid"

cboEmp2.BindingContext = New BindingContext
cboEmp2.DataSource = m_dataSet.Tables("Employees")
cboEmp2.DisplayMember = "lastname"
cboEmp2.ValueMember = "employeeid"

This code rewrites the aforementioned example of the two ComboBoxes sharing the same data. By giving each control a distinct binding context, you keep them out of sync.

Cross-Form Binding

All the examples I have considered until now are based on a single form. Can you extend this binding model to work over multiple forms? Yes. The feature revolves around the scope of the binding context. The binding context is a collection of bindings stored in a given control?the form in the simplest case. This means that two distinct forms have distinct binding contexts. Or, at least, this is the default scenario.

Imagine a form that loads and displays some data?for example, all information about customers or employees. Users select a particular record and click to get more details. In this case, though, the application supplies the details view as a separate modeless form. You would like to select a new employee in one form and see his details in the other form, and you'd like to do this with virtually no code and automatically as before. Is this possible? Of course.

The code snippet below shows the code you can use to open the details form.

Sub Button1_Click(ByVal sender As Object, _
                  ByVal e As EventArgs) _
      Handles Button1.Click
   Dim f As New DetailsForm
   f.SetData(BindingContext, m_data)
   f.Show()
End Sub

SetData is a custom method defined on the application-specific DetailsForm class. The method takes a couple of parameters?the binding context of the parent form and the data source. Listing 2 shows the full source code of the DetailsForm class.

The DetailsForm class has a couple of custom members that are set using SetData. These internal members are placeholders for the parent form's data source and binding context. These members are used when the form loads?an event triggered by the Show method?to configure the details form child controls. In particular, the binding context collection of the details form is set to the same binding context as the parent. All data-bound controls in the details form are bound to the parent's data source. In the end, the two forms now share binding context and data source objects. As a result, the binding model works as expected irrespective of the different windows involved. (See Figure 3.)

Figure 3: Users select a record on the parent form and child records automatically display in the secondary form.

Special Binding and Formatting

Simple data binding between a control property and a data field is implemented through a Binding object. As mentioned, the binding class defines a logical link between a pair of names. Live objects are attached to both names only when the Binding object is added to the DataBindings collection of a physical control.

This said, how does the data binding manage this? More precisely, what if type and format of the input data (the data field) don't match the expectation of the control property? Let's review a first relatively simple case.

Suppose you have a TextBox control and a date field. You can create the Binding object as follows.

Dim b As Binding
b = New Binding("Text", dataSet, _
                "Employees.Birthdate")
txt.DataBindings.Add(b)

By default, the TextBox control displays a date string similar to the following:

2004-6-19 12:00:00AM

What if you want to give it a different format? The Binding class provides a couple of events you can handle to pre- and post-process the data being displayed through the control. The preprocessing event is Format; the post-processing event is Parse.

The following code snippet shows how to write a Format event handler to change the format of a date field.

Sub HireDate_Format(ByVal sender As Object, _
                    ByVal e As ConvertEventArgs) _
        Handles txt.Format
    ' Format the hire date into a more
    ' convenient output format
    Dim dt As DateTime
    dt = CType(e.Value, DateTime)
    e.Value = dt.ToString("MMM dd, yyyy (ddd)")
End Sub

The event handler must be a delegate of type ConvertEventHandler which carries over a parameter type of ConvertEventArgs. The structure has one key and read/write member?Value.

When the event handler executes the Value field contains the originally formatted data coming from the data source. You can cast or format it at will and you can store the modified value back into the Value member. What you store in Value will actually be passed to the bound control. In doing so, make sure the passed value has a type compatible with the bound control property. Do you want one more example illustrating just this point? Read on.

Suppose now you have to bind a BLOB field to a PictureBox control. The bindable property of the PictureBox control is Image, which requires an object of type System.Drawing.Image. You cannot get an instance of this object out of any database (prior to SQL Server 2005).

In this case, the code for the Format event is a bit more sophisticated, as Listing 3 demonstrates.

The original data comes in as an array of bytes. Since you cannot bind an array of bytes to a PictureBox, you need to transform it into an Image type. Additionally, you cannot create an Image type out of a stream of bytes. That's why in Listing 3 I use an intermediate MemoryStream object to contain the bytes and use them to initialize a System.Drawing.Bitmap object that you can bind to a PictureBox control. (Note that Image is the base class of Bitmap.)

The Parse event requires the same delegate as the Format event. The event is triggered before the data in the bound control saved to the in-memory data source. In this case, the expression e.Value contains the client value that you can format or cast to match the type expected in the corresponding field of the data source.

Conclusion

Windows Forms data binding is a feature with many facets. You can associate data-bound expressions with properties of controls no matter the underlying type and you can include text and Image types. You also have events to control how the data is marshaled back and forth between the user interface and the back end memory.

The DataGrid, ListBox, and ComboBox controls allow you to associate an entire data source to their user interface. You can use any of the data sources that you can use in ASP.NET applications.

The data binding mechanism is ruled by a component known as the binding manager, which groups together all controls in a form that share the same data source object. To these control, the binding manager provides properties and events to make them capable of detecting each other's changes and selection changes. Thanks to this feature you can build master/details views as well as complex and inter-related user interfaces more quickly and efficiently. Your controls can auto-update their user interface reflecting changes in other controls. It's not magic; it's pure smart code.