inding data to user interface widgets is only part of the story in complex applications. Many analysis and design methods result in objects that contain both data and behaviour. DataSet and DataReader objects only capture the data of your application, not its specific behavior. Don’t get me wrong. I have nothing against ADO, DataSets, or DataReaders?they are extremely valuable, and they’re easy to data bind; however, if your application model contains objects with both data and behavior (methods) you still need to be able to bind those objects to the user interface of your application.
In this article you will see that connecting complex object architectures to Web and Form controls is just as straightforward as binding to DataSets or DataReaders. I’ve used Web-based (ASP.NET) examples because the programming model presents a number of unique challenges, but many of the examples and techniques apply equally well to both Web and Windows Forms applications.
Data Binding Basics
The simplest binding connects a control property value to the value of an object. That object may be a simple string attribute of the page, or a member of a complex object hierarchy. For example, probably the most common binding connects the text value of a control to the text of an objects property or field.
As an example consider an ASP label bound to a property of the form class. First, assume you have a MyForm.aspx page containing the presentation and a code-behind file called MyForm.aspx.cs. You’ll often see hand-written ASP code using the syntax shown in the following example.
When the ASP runtime builds the page, it evaluates the value of MyProperty and sets the Text property of the control to the string value of MyProperty by calling the ToString() method.
The companion code-behind page might look something like:
public class SimpleDataBinding : System.Web.UI.Page { protected System.Web.UI.WebControls.Label Label1; protected string MyProperty { get { return "MyProperty Value"; } } private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { DataBind(); } } Web Form Designer generated code }
Note that the Text attribute of the control gets assigned to the property value only during the DataBind method call. Any previously existing value (such as in the case of a TextBox) is simply overwritten by the binding call. You might be wondering why the code implements the MyProperty member as a property rather than a simple field. It’s because Visual Studio-generated code makes a call to the DataBinder.Eval() method rather than accessing the class member directly. The DataBinder.Eval method looks only for properties with matching names, and is unable to make use of fields declared in the code?in other words, using a field doesn’t work. Compare the following example (generated by the Visual Studio IDE) to the first example page shown earlier.
For the code to run properly, you must also alter the SimpleBinding class to make MyProperty public, because the Eval() method can’t access protected properties. See the SimpleBinding2.aspx file in the downloadable sample code.
So, binding the value of a control to a form property is fairly straightforward but hardly satisfactory for a complex application. Applications often require many bound controls, and the properties aren’t likely to be so easily accessible; they’re far more likely to be scattered across a hierarchy of objects and embedded in collections.
Creating More Complex Bindings
You aren’t limited to simple value/control property bindings. Here’s an extension of the preceding example that changes the property type to a collection of stings that become the values of a DropDownList control. The .aspx page code looks like this:
The supporting class, SimpleCollectionBinding, looks like this:
public class SimpleCollectionBinding : System.Web.UI.Page{ protected System.Web.UI.WebControls.DropDownList DropDownList1; public StringCollection MyStrings { get { StringCollection col = new StringCollection(); col.Add("Option 1"); col.Add("Option 2"); col.Add("Option 3"); return col; } } private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { DataBind(); } } #region Web Form Designer generated code #endregion}
The SimpleCollectionBinding code makes a direct reference to the collection, so the property can have protected rather than public scope. During the call to DataBind, the DropDownList object looks for one of its supported interfaces (IEnumerable or IList) and once found, iterates over the objects in the collection calling the ToString() method to obtain the text values for the control to use. Again this is a very simple example which does not make use of the DataMember, DataTextField, DataTextFieldFormatString, or DataValueField properties of the control.
Now that you’ve seen how to bind a collection, here’s a more complex object that can be used to fill the DropDownList. The DropDownListItem class shown below fills the values of the DropDownList control. When filled, the control should display a list of dates obtained from the class’s Date property and should use the index value internally to identify which item the user has chosen..
public class DropDownListItem { public DropDownListItem(DateTime date, int index) { this.date = date; this.index = index; } public DateTime Date { get { return this.date; } } public int Index { get { return this.index; } } private DateTime date; private int index; }
The form code contains a single property that the DropDownList control uses as its data source:
public class SimpleCollectionBinding2 : System.Web.UI.Page { protected System.Web.UI.WebControls.DropDownList DropDownList1; public SimpleCollectionBinding2() { // Build a collection of collections. // The first collection contains a single // named member matching the DataMember property // value. In this case 'DataMember' // The second collection contains the values. this.dataSource = new DropDownListItemArray(); this.dataSource.Add(new DropDownListItem( DateTime.Now, 0)); this.dataSource.Add(new DropDownListItem( DateTime.Now.AddDays(1), 1)); this.dataSource.Add(new DropDownListItem( DateTime.Now.AddDays(2), 2)); } private DropDownListItemArray dataSource; public DropDownListItemArray DataSource { get { return this.dataSource; } } private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack){ DataBind(); } } #region Web Form Designer generated code #endregion }
To complete the picture, the DropDownList control references the Form’s DataSource property using the Date property for the displayed text values and the Index property as the corresponding DataValue.
Binding to Hierarchical Data
One common programming scenario is a requirement to display data contained in a hierarchy of objects. The root of the hierarchy may be a single object or collection of objects. Each root object then contains one or more children, and so on.
The Windows Forms DataGrid control understands hierarchical data and has built-in properties and methods that allow users to navigate through the object tree. The ASP.NET Grid does not make life that easy. You have to add a Grid to each of the columns containing a collection of objects. The Visual Studio IDE cannot currently cope with this more complex arrangement, so you have to code it yourself.
The following ASP example shows how to build a simple hierarchical display of the data using multiple DataGrid controls. This example uses a two-level hierarchy but you can extend the principle to any desirable depth.
In this example, the hierarchy consists of two objects: a Customer object and a strongly typed collection of that customer’s Orders. Here’s the ASP code:
Here’s the code-behind page that supplies the data.
public class TreeGrid : System.Web.UI.Page{ protected System.Web.UI.WebControls.DataGrid DataGrid1; public TreeGrid() { Customers = new CustomerCollection(); Customers.Add(new Customer("Graham", "Brooks", "[email protected]")); Customers.Add(new Customer("A", "Customer", "[email protected]")); } public CustomerCollection Customers; private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { DataBind(); } } #region Web Form Designer generated code #endregion }
The page consists of two grids, one nested inside the other and defined as a template column. When you bind the outer grid to a Customer object the inner grid gets bound to the order collection property of the customer object using the DataBinder.Eval expression. The inner grid then iterates over the order collection displaying the properties of the order.
User Controls and Other Bindings
![]() | |
Figure 1. Binding User Controls: The figure shows a user control containing a single grid with a template Orders column. Note that it displays both orders and order details. |
Nested grids are very useful, but sometimes you need more control over the layout and presentation of the data?and some support from the IDE. User controls can provide this flexibility. An added bonus is that you can use the IDE to develop the control, defining the grid columns, etc. The following example uses a custom control to display the Order object in a grid that also displays the order details (see Figure 1).
The CustomerOrders.aspx file contains a single grid with a template Orders column containing a reference to the OrderViewControl.ascx control for both the item and edit template. I find it easiest to drag-and-drop the control onto the page and then copy the control to the template column built with the property builder. This ensures that the page references the control properly.
Author’s Note: In the control’s Page_Load method you must make an explicit call to the DataBind() method of your grid control. Calling the control’s DataBind method doesn’t seem to work!
private void Page_Load(object sender, System.EventArgs e){ if (!IsPostBack) { OrderGrid.DataBind(); }}
For comparison, the sample code includes a nested grid version implemented in the single TreeGrid.aspx file.
So far, you’ve seen how to bind a class property value to a control. The .NET framework provides a much richer set of bindings that this. In fact, you can bind any property of a control to any property in an object collection. This is particularly useful if you want to emphasize object state visually. To maintain a clean separation between presentation details and object implementation you can use stylesheets to add the visual emphasis. The following example binds the value and style name from an object to the control. How the value is displayed is then a function of the stylesheet rather than the control and object model.
Label
Here’s a slightly more complex example that uses an integer property value to determine which CSS class to apply. Note that this has the advantage of placing all knowledge of how the value is to be presented to the user into the interface?but it also moves the decision on what should be highlighted out of the object, which may not be desirable.
Label
Collection Interfaces
Many controls work with object collections as data sources. For data binding purposes, you can define a collection by the interfaces it supports. Two interfaces are particularly important: IEnumerable and IList.
IEnumerable is really a gateway interface supporting a single method called GetEnumerator that returns an object that implements the IEnumerator interface. IEnumerator contains one property, Current, and two methods, MoveNext and Reset. The initial call to MoveNext moves the pointer to the first object, and subsequent calls move the pointer to the next object in the collection. The method returns false when there are no more items in the collection. As long as MoveNext returns true the control uses the Current property to access the object.
Some controls require that the collection support the IList interface, (such as ListControl and ComboBox). IList provides the control with more flexibility in how it retrieves the required data. IList is a descendent of ICollection, IEnumerable. The control typically makes use of the ICollection.Count method to get the number of items in the collection and then uses the Item method (indexer in C#) to obtain the contained objects.
Given that data sources are characterised by interfaces rather than classes, you can map data sources onto almost any kind of data or object. The objects returned by the interface do not need to be of the same type?they just need to support the property or properties you specified the control should read for its values.
Flattening Data for Display
I am sure there’s a better term for this concept, but the idea is that you want to display a collection of values in a single control. For example, in most cases it’s convenient to store an address as separate strings such as postal code, city, street address, etc., but for display purposes, you would often want to “flatten” the values, concatenating and displaying them as a single string in a Label, TextBox, or other control.
The Address class below shows one solution. Internally it stores the various address parts in a StringCollection, but binding to the object calls the ToString() method which combines the address lines into a single string. If you have an existing model or third-party library that you can’t change, you can write an adapter class to map the multiple strings in the collection to the control.
public class Address{ public Address() { this.addressLines = new StringCollection(); // Example Address this.addressLines.Add("Tadcombe"); this.addressLines.Add("UK"); } public Address(StringCollection addressLines) { this.addressLines = addressLines; } StringCollection addressLines; public override string ToString() { string result = ""; foreach (string line in addressLines) { if (line.Length > 0) { if (result.Length > 0) { result += ", "; } result += line; } } return result; }}
The file CustomerOrders2.aspx in the sample code demonstrates binding the Address value to a GridCell, but the principle applies just as well to other single-valued controls.
If you wanted the address to be split into separate lines you could use a repeater control bound to the collection with your single-valued control displaying each element of the collection.
The sample code contains working examples of the techniques outlined. The strongly typed collections were generated using CodeSmith Explorer which makes light work of generating the code.
The samples are all written in C# but mapping to another .NET language should be reasonably straightforward.
Although the process to connect arbitrary objects and object hierarchies to user interface controls in the .NET framework is straightforward, you’ll find there are a few stumbling blocks to catch the unwary. First, remember to call DataBind() either on the form or user control directly. Expose the attributes to which you want to bind control values as public properties rather than as fields to allow both direct referencing and indirect referencing using DataBinder.Eval().