Bind Data to WinForms Controls
Display data programmatically with only a few lines of code, using the databinding features built into the .NET Framework's WinForms controls.
P>Technology Toolbox: C#
You can use business data to transfer information from one place to another, do business calculations, drive automated systems, or simply present quantitative information to users. The last task has become a whole lot easier with the .NET Framework.
Controlling data display used to mean writing a lot of code. You had to iterate over a set of data and push individual values into various types of display controls. This tedious and error-prone process consumed a lot of effort for something so fundamental to many business applications.
The .NET Framework helps alleviate this effort. .NET supplies automatic data binding as a core feature for both Windows and Web controls. You can use this feature with a few common coding patterns, making it much easier to push data into displays in .NET applications.
Here I'll discuss databinding with Windows Forms controls. You'll learn how to wire up data in various forms to various controls, and get into the loop as data is being inserted into individual controls so you can modify it before it's presented. I'll also show you how to programmatically control paging the current record within a data source, and how to wire up master-detail binding between two grids.
It all starts with databinding. WinForms databinding in .NET includes both simple and complex binding. The difference depends on the complexity of the data and its presentation. You can't use complexity of the code as a criterion, because usually you can wire up controls to display data with only a few lines of code, thanks to .NET's consistency in the design of data and collection classes, and its inclusion of databinding mechanisms in the control.
The data collections you bind to should implement the IList interface for the widest and richest databinding support from controls. Most .NET Framework data objects (including DataSet, ArrayList, and custom collections derived from CollectionBase) implement IList for you already.
Oddly enough, the richest and most complex databinding you can do on WinForms can also be the simplest. Let's say you want to bind a DataSet to a DataGrid on a form. You display that data simply by setting the DataGrid's DataSource property to a reference to the DataSet or one of its contained DataTables:
Binding a set of data against a ListBox or ComboBox provides another example of supposedly complex databinding. Here you usually want to display a single column as the text in the control. Often another column contains values you want to keep associated with the items displayed (such as a primary key ID column). The .NET controls support this scenario by exposing a DisplayMember property to identify the names of the columns to display the data, and a ValueMember property to hold the associated values. The column identified as the DisplayMember converts automatically to a string for display, but the column identified for the ValueMember is maintained as an object reference. This process maintains the true type of the column; you can use it when retrieving the current item's value:
listBox1.DisplayMember = "au_lname";
listBox1.ValueMember = "au_id";
Bind Complex Data Easily
The same process works for custom object collections that implement the IList interface. You use a similar process, setting the DataSource property to a reference to the collection containing the data, then setting the DisplayMember and ValueMember properties to the names of properties on the objects in the collection.
You might be tempted to treat ListView or TreeView controls as complex data binding controls because they expose a DataBindings collection inherited from the Control base class. However, these controls differ in that each item in the control is rendered as a separate control. So the normal databinding I've just described won't work for ListView controls. In most cases, you must iterate programmatically through your data and add Items and SubItems to the ListView control to get it to display your data.
Now I'll turn to simple databinding, which refers to binding the current value of an object or collection of objects to a single-valued control, such as a TextBox, CheckBox, or RadioButton. You display the current value of a data source field or property by creating Binding objects and adding them to the DataBindings collection. The collection itself exposes an overloaded Add() method that lets you create and add a Binding with one line of code:
Creating a binding includes passing the property name on the control you're binding to the target property (the name you want to set), the source (the field or property name to pull the data from), and the underlying data source.
You can add multiple databindings to a control. For example, you might populate the Text property of a CheckBox control using a field from one data source, then set its Checked property based on a collection of Boolean values from another data source.
The Binding class exposes the Format and Parse events, which let you modify values before they're displayed or before changes push back into the underlying data source. Format lets you modify what's actually set against the property of a control because it's called just before the data is displayed. Parse is called when the bound control value has changed and the value is going to be pushed into the underlying data source. This happens when focus leaves the control or when the current position in the data source is changed through a selection event or paging the data (I'll discuss this later).
The sample application accompanying this article demonstrates most of the forms of databinding you might be interested in performing. Let's say you bind a textbox to the Authors table's au_state field, but want it always displayed in uppercase. You also want any new values entered to go into the data source as uppercase values. You can accomplish this by constructing a Binding object matching the TextBox control's Text property to the data source's au_state field. Set up a handler for the Format event that converts data to uppercase before it's displayed. Also, set up a handler for the Parse event that ensures that all values are pushed back into the underlying data source as uppercase, whether or not they started out that way (see Listing 1).
My sample application lets you select data items in one control on one of the form's tabs. The current item in other controls bound to the same data source changes accordingly, depending on which item you select. You can Accomplish this kind of databinding using the BindingContext found in each Windows Form. BindingContext holds a collection of objects derived from BindingManagerBase. Each object keeps track of the current item for every data source bound to controls on the form (see Figure 1). The type of these objects is either CurrencyManager or PropertyManager, depending on whether the data source is a list-oriented source or single-property value, respectively. The CurrencyManager is the most common case for data collections.
By default, BindingContext synchronizes all controls that are bound to the same source to the current item. This way, if you change the selected item in a list of values bound to a source, the system changes the current or selected value of all other controls bound to that same source. You can set up multiple CurrencyManagers for a single source on a form if you need to, but this is rare and gets complex to manage.
You can use CurrencyManager to set the current record's value in a data source programmatically, updating all controls bound to that source as well. You do this by accessing the CurrencyManager for a given source, then using the Position property to do the update. For example, you could use this procedure to embed a page forward/back button in a form (see Listing 2). However, the CurrencyManager and tab controls on WinForms have known problems that can impact setting CurrencyManager's Position property.
For example, my sample app uses the paging controls on the Paged Data tab, but the ListBox on that tab doesn't always update with the current record. If you switch to another tab and come back to the Paged Data tab, the ListBox on that tab updates correctly, showing the current record in the CurrencyManager. This usually occurs only if you have a form with a Tab control on it with multiple data sources bound on different tabs. The problem is intermittent and often hard to duplicate.
Bind Data Hierarchically
You might want to do one more form of databinding: a master-detail view of hierarchical data. I usually have two DataGrids on a form, where one grid contains the parent rows and the other contains the child rows related to the currently selected parent row. Keeping the child rows synchronized with the parent row is a snap thanks to the DataGrid control's built-in databinding capabilities.
You set up a custom binding on a DataGrid by calling the SetDataBinding() method on the grid. For normal databinding, SetDataBinding() takes the data source and name of the table within the data source you want to bind to.
You could do the same thing more simply by setting the DataSource property to the DataTable. However, you might want to take advantage of the additional capability SetDataBinding() exposes. Instead of passing in a table name, you can pass in the name of a parent-child relation on the DataSet. This binds the grid in a master-detail mode to the parent table row selections. You specify the relation by providing the "path" to the relation, prefixing the name of the relation with the name of the table on which the relation is set. You don't need to set up a foreign key constraint for the master-detail mechanism to workjust a relation:
That completes this quick tour of WinForms databinding. Microsoft announced .NET Framework 2.0's new features recently, which include some significant improvements in this area. Your existing code and controls will continue to work, but you'll gain new options, including a new DataContainer object that maintains its own BindingContext, the new DataGridView grid that's both easier to use and more powerful than today's DataGrid, plus a variety of other data-related controls.
About the Author
Brian Noyes is a software architect with IDesign Inc., a .NET-focused architecture and design consulting firm. Brian specializes in designing and building data-driven distributed Windows and Web applications. Brian is working on a book for Addison-Wesley on building WinForms data applications with .NET 2.0. Reach Brian at [email protected].