Data Binding in Windows Forms 2.0


Introduction

This article is intented to illustrate the principles of data binding in .NET Windows Forms 2.0 as well as a description of data set and data-bound controls. Data binding techniques are sometimes one of the most important part of many enterprise applications and many developers don't understand it at all. Data binding is the mechanism for automatically associating and synchronizing data objects in memory with controls for presenting and gathering data from the user interface.

Most of the controls in the Windows Forms architecture support the data binding mechanism. They inherit from the Control class which implements the IBindableComponent interface which in turn supports a collection of binding objects (instances of the Binding class) through the Data bindings property as well as it allows the association of the underlying control with the binding context (a singleton instance of the BindingContext class) which synchronizes controls bound to the same data source. Complex controls can also add their implementation to advanced data binding through additional properties such as DataSource, DataMember, DisplayMember and ValueMember. This mechanisms enables binding of any control's properties to a data source.

To set up a data binding mechanism in Windows Forms, you need a data source. A data source is an in-memory business object that either comprises other business objects or it may be a single instance of a business object. The most common type of data source or business object in .NET is the instances of Dataset class or derived typed DataSet class.

The principles of Data binding

You can use the data binding mechanism to associate a business object to a control. In Windows Forms, data binding allows the data flow between the data sources and the data-bound control in two ways, namely, you present the data from the data source in the user interface, then interact with the control and change some values of its properties and finally those changes are reflected back in the underlying data source automatically.

To illustrate these concepts, we're going to use the AdventureWorks database shipped with the Microsoft SQL Server 2005 installation. First of all, let's create a Windows application using Visual Studio .NET 2005 IDE, and a type DataSet class to manage the Person.Contact, Purchasing.PurchaseOrderHeader and Purchasing.PurchaseOrderDetail tables representing the business entities Contact and Purchase Order as shown in Figure 1.

DatabindingFigure1.gif

Figure 1

There are two forms of data binding in Windows Forms: simple and complex data binding.
Simple data binding maps a property on a control (Text property of a TextBox control) to a property in a data source item. It shows only one row at a time.

This type of binding is represented by the Binding object. The constructor of the Binding class takes three parameters: the name of the control property, the data source, and the navigation path as shown in Listing 1. For example, let's bind individual columns within a table to the Text property of Textbox control.

      Binding bdContactId = new Binding("Text", this.m_dsCRM, "Contact.ContactID");
            this.m_tbContactId.DataBindings.Add(bdContactId);
            Binding bdContactLastName = new Binding("Text", this.m_dsCRM, "Contact.LastName"); this.m_tbContactLastName.DataBindings.Add(bdContactLastName);

Listing 1

The data source and the navigation path can be specified by various combinations, for instance in Listing 2, both binding construction statements will display the same information, although the way the data source and navigation path are specified to the data binding mechanism is different for each of the two controls and it may cause problems with synchronization among the data-bound controls. The data binding mechanism creates a separate synchronization object (either a CurrencyManager for list data source or PropertyManager for individual object data sources) for each data source because the data sources are specified differently between the bindings.

To avoid this situation, you need to be consistent in the way you specify your data sources and navigation paths, and thus you get synchronized controls. I prefer to use the first option of the Listing 2.

            Binding bdCustomerFullName = new Binding("Text", this.m_dsCRM, "Contact.LastName");
            Binding bdCustomerFullName = new Binding("Text", this.m_dsCRM.Contact, "LastName");


Listing 2

Another approach to setting the DataBindings property of controls is to use the Visual Studio UI property dialog. For example, to set up the DataBinding property of the TextBox control m_tbContactId then click on the control and press F4 for the Property page. Click on the (DataBindings)/(Advanced) node in the property page (see Figure 2).

DatabindingFigure2.gif

Figure 2

When you navigate to the correct data provider object (in this case, m_dsAdventureWorks, Contact.Lastname), then an instance of the BindingSource object is created in Visual Studio 2005. The BindingSource class is explained in details below. You can also define the format of the data to be displayed in the control.

Complex data binding is the association of a list of data items bound to a control for the presentation of one or more properties at a time, thus it's important to display an entire rowset. Not all the controls support complex binding, and those in this category are of two types: grid controls (DataGridView in VS 2005 and DataGrid in VS 2003) and list controls such as ListBox, ListView and ComboBox.

A Control which supports this type of binding has the DataSource and DataMember properties to map the data source to the control corresponding to the data source and navigation path arguments of the Binding constructor. Some of them have also the DisplayMember property to display the only one column of the table on the data source and ValueMember property that contains the encoded value that correspond to the DisplayMember as shown in Listing 3.

            this.m_cbCombo.DataSource = this.m_dsCRM;
            this.m_cbCombo.DataMember = "Contact";
            this.m_cbCombo.DisplayMember = "LastName";
            this.m_cbCombo.ValueMember = "ContactID";

Listing 3

Synchronizing data between controls

When you create a binding, the form itself creates synchronization objects for the synchronization of multiple controls on the form bound to the same data source.

Every control has a BindingContext property that holds a collection of synchronization objects (instances of BindingManagerBase) for each data source that is used for data binding on the form. The BindingManagerBase class is the base for CurrencyManager for list-based data sources as well as PropertyManager class for individual custom business objects. You can use these classes to access underlying data, determine the current row and change the current row and affect all the controls bound to this data source.

In .NET 1.X, you had to deal directly with the BindingContext but in .NET 2.0 you don't.

Let's illustrate the concepts with an example. To retrieve a BindingContext instance, you pass a data source and optionally the navigation path. For example to reference the BindingManagerBase object associated to the data source and navigation path on the Listing 1, you must write the code shown in Listing 4.

            BindingManagerBase bmbContact = this.BindingContext[this.m_dsCRM, "Contact"];

Listing 4

As you can see, it is almost the same parameters of the constructor of the Binding but dropping the final level of the BindingMember hierarchy. After you have a reference to the BindingManagerBase then you can set the Position property to move the cursor to records in the data provider.

In Visual Studio 2005 you can do the same operations easily using the BindingSource class and its underlying properties (Position and Current) and methods (MoveNext and MovePrevious) as shown below.

BindingSource class in .NET 2.0

As you can see before, you can spend a lot of time trying to manage data binding in .NET Framework 1.0 and 1.1 with Visual Studio 2003.
One of the most important features introduced in Framework 2.0 along with Visual Studio 2005 is the BindingSource class which is especially useful to deal with complex entities. The BindingSource class serves several purposes:

  • Another layer of abstraction between the bound controls (Win32 and ASP.NET controls) and data sources.
  • A set of services to navigate through the rows, expose current row, pass current row values to bound controls, and pass updated values from bound controls to data sources.
  • A set of services to filter and sort data.
An instance of the BindingSource acts as a proxy between bound controls and their associated data source. You bind the controls on the form to a binding source and then the binding source to a data source. Let's translate the code written in Listing 1 to this new approach as shown in Listing 5.

           //At the class level as an attribute.
            BindingSource m_bsContact = new BindingSource();

            //At the initialization method.
            this.m_bsContact.DataSource = this.m_dsCRM;
            this.m_bsContact.DataMember = "Contact";

            Binding bdContactId = new Binding("Text", this.m_bsContact, "ContactId");
            this.m_tbContactId.DataBindings.Add(bdContactId);

            Binding bdContactFullName = new Binding("Text", this.m_bsContact, "LastName");
            this.m_tbContactLastName.DataBindings.Add(bdContactFullName);

Listing 5


It's remarkable to say that a lot of the code associated with BindingSource instances, as shown in Listing 5, can be generated using drag-and-drop mechanisms from the Data Source page to your Windows and ASP.NET forms.

The BindingSource class has a set of methods which allows navigating through the data source. In .NET 1.X, when you want to navigate the data source, you have to get a reference to the CurrencyManager object for the data source you are working with and change the value of the Position property as shown in Listing 6.
This mechanism is still for backward compatibility with existing code.

          BindingManagerBase bmbContact = this.BindingContext[this.m_dsCRM, "Contact"];
            bmbContact.Position += 1;


Listing 6

To navigate the data source through the BindingSource object, you need to write the following statement as shown in Listing 7.

           this.m_bsContact.MoveNext();

Listing 7

If you don't want to write this code for every application, you can use the new control available in .NET 2.0 BindingNavigator which is a special implementation of the new ToolStrip control. It has a toolbar button to navigate the data source (moving first, last, previous and next), an edit box to enter the position explicitly as well as buttons for adding, deleting and saving data items.

You can also fill a BindingSource with a data reader as well as with a DataTable by setting the DataSource and DataMember properties in Listing 8 and Listing 9.

           string strConnString = "server=localhost;database=AdventureWorks;trusted_connection=true";
            using (SqlConnection objConn = new SqlConnection(strConnString))
            {
                SqlCommand objCmd = new SqlCommand("select ContactID, LastName, EMailAddress from Person.Contact", objConn);
                objConn.Open();
                SqlDataReader objReader = objCmd.ExecuteReader(CommandBehavior.CloseConnection);
                BindingSource bsContact = new BindingSource();
                bsContact.DataSource = objReader;

                this.m_dgvContacts.DataSource = bsContact;
                this.m_tbContactLastName.Bindings.Add("Text", bsContact, "LastName");
            }

Listing 8

            this.m_taContact.Fill(this.m_dsAdventureWorks.Contact);
            BindingSource bsContact = new BindingSource();
            bsContact.DataSource = this.m_dsAdventureWorks;
            bsContact.DataMember = "Contact";
            this.m_dgvContacts.DataSource = bsContact;
            this.m_tbContactLastName.Bindings.Add("Text", bsContact, "LastName");

Listing 9

It's remarkable to say that this approach is not a best practice to follow because it introduces a tight coupling between the presentation layer and the data access layer and this new mechanism is useful when dealing with complex hierarchies of bound controls.

Representing Entities in a Form

In any data model, it is common to find complex business entities which are depicted as one-to-many relationships. The classic example is the complex business entity Order, which is typically represented by OrderHeader and OrderDetails tables that have a one-to-many relationship like it's represented in our data set in the Figure 1. You can also find relationships between business entities which are modeled as a one-to-many relationship.

The classic approach for representing these complex business entities or the one-to-many relationship between entities in forms is named master-detail. It allows displaying a single row of the one-side (master) of the relationship and the many-side (detail) is either displayed one at a time, allowing the user to navigate among them, or you can display them using a list control such as DataGridView, ListBox, ListView or TreeView controls.

You can do master-detail data binding by dragging and dropping the PurchaseOrderHeader entity from the Data Sources windows into the target form in a Details form (see Figure 3) and the PurchaseOrderDetail in DataGridView form (see Figure 4). Then run the application and see the results (see Figure 5).

DatabindingFigure3.gif

Figure 3

DatabindingFigure4.gif

Figure 4

DatabindingFigure5.gif

Figure 5

You can do the same results programmatically using a technique named Chaining Binding Sources for master-detail data binding. You need to chain together two binding sources, with one binding source bound to the parent data source, and the child binding source bound to the parent binding source, and setting the data member property of the child binding source to the property name to the actual name of the data relation between the parent and the child (see Listing 10).

            this.purchaseOrderHeaderBindingSource.DataSource = this.sales_DS;
            this.purchaseOrderHeaderBindingSource.DataMember = "PurchaseOrderHeader";
            this.purchaseOrderDetailBindingSource.DataSource = this.purchaseOrderHeaderBindingSource;
            this.purchaseOrderDetailBindingSource.DataMember = "FK_PurchaseOrderDetail_PurchaseOrderHeader_PurchaseOrderID";

Listing 10


One-To-One relationship

This type of relationship is relatively rare in relational databases. They are used to represent the object-oriented concept of inheritance. Some database designers include all the attributes of the subtype and base type entities in one entity represented by a relational table but there is the drawback of sparse arrays; many rows where the majority of the columns are empty. This problem can increase if the inheritance involves several subtypes.

For example, let's illustrate the concepts supposing that we model the Person base entity (base class of Person), and two other subtypes Seller and Customer (classes of Seller and Customer). A Seller instance is also a Person as well as a Customer instance is also a Person.

One way to deal with this problem is based on the assumption that users will almost always be concentrating on one Entity instance at a time, so you need two forms: one to display the properties of the Seller entities and another one to display the properties of the Customer. Of course, you need to make it easy to navigate between the two forms.

You need to add a discriminator field to the base entity Person as well as it's a best practice (and it's also logical according to the object-oriented concepts that an object has an identity independently of its classification) to use the same primary key as the identifier of entities in the underlying tables representing the base and subtype entities and define a foreign key relationship to enforce referential integrity between the parent table (base entity) and the children tables (subtypes entities). This model can be done easily using the E/R Studio modelling tool (see Figure 6).

DatabindingFigure6.gif

Figure 6

Finally, you apply the same techniques explained in a one-to-many relationship section (see Figure 7).

DatabindingFigure7.gif

Figure 7

Many-To-Many relationship

You can find many-to-many relationships modeling the relationship among business entities but it is not common to find many-to-many relationships representing one complex business entity instead it represents relationships among entities.

One solution is to use a pair of a list controls and allow the user to determine which one filters the other.
Another solution is to flatten the many-to-many into a one-to-many relationship. The one-side part is determined from the point of view of the business process associated to the application. The approach is to ignore the many-to-many relationship and resolve it pretending to be working with a one-to-many relationship.

Let's illustrate the concepts by modelling the relationship between a Person and its Role in an organization. A person may play many roles and a role may contain many persons. You can model the many-to-many relationship in the logical data model as non-specific relationship in E/R Studio (see).

When the physical model is generated from the logical model, then a join table is established in a many-to-many relationship among the entities (see Figure 8).

DatabindingFigure8.gif

Figure 8

To represent these entities in a form, the first step is to determine the point of view of the user from the model of the business process (which side of the relationship is most important for the user). Let's suppose the user is working with roles and he wants to see its underlying Persons. It's remarkable to say that it's not common to require representing both entities at the same time. The next step is to flatten the many-to-many relationship. In this case, the one-side of the relationship is the Role table and the many-side of the relationship is virtual many-sided created by joining the join-table to the Person table (see Listing 11).

select [Person Role].RoleID, [Person].*
from [Person Role] inner join [Person] on [Person Role].PersonID=[Person].PersonID;

Listing 11

Now let's define the DataSet with the tables Role and the virtual table represented by the query in Listing 11. The final DataSet is depicted in Figure 9.

DatabindingFigure9.gif

Figure 9

Let's see the final result in Figure 10.

DatabindingFigure10.gif

Figure 10

Conclusion

In this article, I deeply discussed the key methodologies and techniques to develop enterprise application using data-binding in .NET Framework for Windows Forms application. I also covered the topic of representing business entities that are persisted in a relational database system onto a Windows Forms and how to use RAD techniques to do this to increase the productivity. After you read this article, you have an insight into data-binding in .NET Framework, and you can begin to develop your customized solutions easily.

Up Next
    Ebook Download
    View all
    Learn
    View all