Data Binding for Windows Forms

This article was originally published in VSJ, which is now part of Developer Fusion.
Developers who are used to working with ASP.NET often complain about the level of manual work required to connect Windows Forms controls to data. In fact, Windows Forms developers have had access to a data binding mechanism since .NET 2.0 was introduced in 2005, but it is still little used. Why is that? The answer is that for many of us the roll-out of .NET 2.0 across the corporate network has only happened relatively recently, and we have yet to take full advantage of it. This article provides an introduction to Data Binding in Windows Forms, using as an example a connection settings dialog from an application.

Binding Architecture

The implementation of Windows Forms in .NET 2.0 is required to be backwards compatible with .NET 1.0 and 1.1, and therefore support for binding had to overlay an existing object and event model. This is in contrast to ASP.NET, where data binding was a part of the architecture from the very beginning. As a result, there are a number of key differences between ASP.NET and Windows Forms that are relevant to data binding, as shown in Table 1.

Table 1: ASP.NET vs. Windows Forms
ASP.NET Windows Forms
Page implementation object always instantiated by framework Form implementation object instantiated by user code
Framework controls object lifecycle User code controls object lifecycle
Events (usually post-backs) are already expensive due to the round trip to server Events are very low cost thanks to the underlying messaging architecture
User actions are typically executed only when the form is posted back to the server User actions are typically processed immediately
Immediate feedback to the user is handled in client side javascript, not the page implementation object Immediate user feedback is handled by the form implementation object

The result of all these differences is that there is a big structural difference in the way that binding is implemented.

ASP.NET supports binding at the page level, so data can be bound to almost any page element. Controls representing collections bind directly to a data source, whether that is a dataset, array, object, or whatever. In Windows Forms all binding takes place through an intermediary object called BindingSource. This object provides the data access and management functions required by Windows Forms controls. It ensures that all updates to control state are reflected in the data, and vice versa.

The connection between the BindingSource and the data source is made by setting the DataSource property. This can be set to point to almost anything, from a DataSet object to a simple custom business object. If the connected object contains more than one list or table, the DataMember property can be used to specify which one should be bound.

BindingSource provides an IList interface (through the List property) which can be used to access the data it wraps. There is also a Current property to access the currently selected member of the list. A summary of many of the other properties and methods of BindingSource is shown in Table 2 to give you an impression of just how many aspects of binding are managed through this object.

Table 2: BindingSource properties and methods
Category Properties Methods
Data Source DataMember, DataSource  
Data Source Properties IsFixedSize, IsReadOnly  
Data Access List, Count, Item Add, AddNew, Clear, Contains, IndexOf, Insert, Remove, RemoveAt, RemoveCurrent
Binding State IsBindingSuspended ResetBindings, ResetCurrentItem, ResetItem, ResumeBinding, SuspendBinding
Currency CurrencyManager, Current, Position MoveFirst, MoveLast, MoveNext, MovePrevious
Sorting IsSorted, Sort, SortDirection, SupportsAdvancedSorting, SupportsSorting ApplySort, RemoveSort
Searching SupportsSearching Find
Filtering Filter, SupportsFiltering RemoveFilter
Allowed Operations AllowEdit, AllowNew, AllowRemove ResetAllowNew
Editing   CancelEdit, EndEdit

Any BindingSource is inherently capable of hosting a list structure. If a single object is assigned to the DataSource property, then the Current property will return that object, and the List property will return a list with that single object in it. A BindingSource object is a .NET Component and is therefore usually created as part of the component model:

bs = new BindingSource( Container );
	((System.ComponentModel.
		ISupportInitialize)bs).
		BeginInit();
// Other Initialisation Code
((System.ComponentModel.
	ISupportInitialize)bs).EndInit();
Note that the BeginInit and EndInit calls are part of the ISupportInitialise interface which is implemented as explicit by BindingSource. Explicit interfaces can only be accessed by using a strongly typed reference, in this case obtained by casting the object.

We can then connect the BindingSource to data object or collection by setting the DataSource:

	bs.DataSource = data;
The connection between a control property and a BindingSource is made by adding a Binding object to the DataBindings collection of the control. This object links the property name to the DataSource and a string representing the data member. In practice however, Binding and BindingSource objects are usually created in the forms editor and the component model code is generated automatically. In order to explore this we’re going to use an example.

Entering a network address

Figure 1 shows a dialog used in a number of places in an application I developed recently to allow the user to input information about a server connection and thus populate a Server object (see Figure 1).

Figure 1
Figure 1: Entering a network address

Any time we build a dialog like this we need to build a mapping between the business object containing the data and the controls on the form. In .NET 1 the only way to do this was to write code that looked like:

public class Server
{
	private string _Host;
	private int _Port;
	private string _User;
	private string _Password;
	private bool _RequireSSL;

	public Server() {}

	public Server( Server s )
	{
		_Host = s._Host;
		_Port = s._Port;
		_User = s._User;
		_Password = s._Password;
		_RequireSSL = s._RequireSSL;
	}

	public Server( string Host,
		int Port )
	{
		_Host = Host;
		_Port = Port;
		_User = “”;
		_Password = “”;
		_RequireSSL = false;
	}
	public string Host
	{
		get { return _Host; }
		set { _Host = value; }
	}

	public int Port
	{
		get { return _Port; }
		set { _Port = value; }
	}

	public string User
	{
		get { return _User; }
		set { _User = value; }
	}

	public string Password
	{
		get { return _Password; }
		set { _Password = value; }
	}

	public bool RequireSSL
	{
		get { return _RequireSSL; }
		set { _RequireSSL = value; }
	}
}
To illustrate the point I have chosen to set the control objects to be public and place the code outside the dialog class – of course you can achieve better encapsulation by moving that code inside the dialog class, but similar code will still exist.

The main problem with this sort of code is that the mapping between controls and data is hidden in the code-behind file, while the controls themselves have been created and are edited in the forms editor. Furthermore these two have to be kept in sync and are linked only by the names assigned to the controls.

Getting in a Bind

The Forms Designer in Visual Studio takes most of the hard work out of setting up a binding for a dialog like this. Looking at the properties for the address control in the above dialog there is a section called Data that did not appear prior to Visual Studio 2005. Opening the DataBindings entry shows the properties of the control that are common targets for data binding. I wanted to use the Text property. Clicking the dropdown button on the property value displays a list of the data sources available in my project, with an option to add a new one (see Figure 2).

Figure 2
Figure 2: Binding the address

As I do not yet have a data source in the project, I will select this Add.. option. The data source wizard is now displayed, and since I want to use the Server object I make the following selections:

  • Data Source Type: Object
  • Object You Wish to Bind to (drill down class view to…):
    • Project NetworkDialog
    • Namespace: NetworkDialog
    • Class: Server

Figure 3
Figure 3: Binding the Address again

After doing this, the properties of Server object appear in the list as shown in Figure 3. As in the mapping in the code:

UnboundForm ubf = new UnboundForm();
ubf.textBoxAddress.Text = server.Host;
ubf.textBoxPort.Text = server.Port.ToString();
ubf.checkBoxSSL.Checked = server.RequireSSL;
ubf.textBoxUserName.Text = server.User;
ubf.textBoxPassword.Text = server.Password;

if ( ubf.ShowDialog() == DialogResult.OK )
{
	server.Host = ubf.textBoxAddress.Text;
	server.Port = int.Parse(ubf.textBoxPort.Text);
	server.RequireSSL = ubf.checkBoxSSL.Checked;
	server.User = ubf.textBoxUserName.Text;
	server.Password = ubf.textBoxPassword.Text;
}
…I will connect the Text property of textBoxAddress control to the Host property of the Server object.

You may now be wondering how I appear to have completed the binding process with no mention of creating a BindingSource object. The answer is that Visual Studio has looked after this for me in the background, and automatically created and configured a BindingSource object called serverBindingSource (see Figure 4).

Figure 4
Figure 4: Automatically created BindingSource

The rest of the controls can be bound in the same way, this time selecting the existing serverBindingSource from the DataSource property window, at which point the code below is all that is needed to display the dialog:

BoundForm bf = new BoundForm();
Server oldValue = new Server( server );
bf.bindingSource1.Add( server );
if ( bf.ShowDialog() != DialogResult.OK )
{
	server = oldValue;
}

Taking control

The first time I used this process, I found it a little alarming just how much Visual Studio had done for me in the context of editing one control, and I wanted to look at how I could achieve the same thing by carrying out each step myself. This gives me the confidence that I understand what Visual Studio is doing and why! It turns out that actually each individual step is quite straightforward:
  • Create the data source manually either by selecting Add New Data Source… from the Data menu in Visual Studio, or by using the icon on the Data Sources window (Show Data Sources from the Data menu or Shift-Alt-D).
  • Add a BindingSource to the form by dragging it onto the canvas from the Data section of the toolbox.
  • Connect the BindingSource to the data source using its DataSource property.
  • Connect the control data bindings to the BindingSource.
Other than object creation and component model code (which is all boilerplate) the only code generated by the example above is:
serverBindingSource.DataSource =
	typeof( NetworkDialog.Server );
textBoxAddress.DataBindings.Add(
	new Binding( “Text” ,
	serverBindingSource ,
	“Host” , true ) );
This assigns a type object to the DataSource member of the BindingSource object as a placeholder so that the GUI can treat the BindingSource as if it was strongly typed, and display the correct members.

I have demonstrated binding to a custom object as this is the sort of binding I use most often in Windows Forms, but the data source wizard also allows you to connect to a database or a web service.

If you go back and look at Figure 2 you will notice that the Text and Tag properties of the control are available for binding directly in the properties editor. This is because these properties are decorated with the BindableAttribute attribute. However, you can bind to any property of a control by using the advanced binding dialog. This is opened by selecting Advanced in the binding section of the properties editor, and clicking the … button that appears. If you do this for the textBoxAddress control you will see the full list of bind-able properties (see Figure 5).

Figure 5
Figure 5: The Advanced Binding dialog

This includes all of the appearance properties for the control, so you could in principle use this as a mechanism for changing the appearance of your application on the fly, or for enabling and disabling groups of controls. Each binding can have a separate BindingSource, so your application could have an object containing colour settings that was bound to the appropriate properties of each control.

You will also see that for each property binding there are a number of additional parameters to control binding. These include a format specification for string types, and defining the value that will represent NULL values in the data.

Binding lists

I’m going to reuse the Server object we have already been working with to demonstrate binding to lists. If you wish to create the BindingSource in the forms editor then the process is identical. This is because even though we only used a single object in the previous example, internally the BindingSource object was still treating it as a list with one entry! The list in this case will be of type List<Server>, and it will be connected to the BindingSource exactly as before.
Server[] initial =
	{
	new Server(“my.host.com”,1111) ,
	new Server(
		“host.interesting.com”,2222) ,
	new Server(
		“service.vsj.co.uk”,3333)
};
List<Server> list =
	new List<Server>( initial );
bindingSourceServer.DataSource = list;
The simplest control used in binding lists is the ListBox, and it can be bound to show the Host member of each Server by setting the DataSource and DataMember properties:
listBox1.DataSource =
	this.bindingSourceServer;
listBox1.DisplayMember = “Host”;
These properties were not the present on single value controls we were binding to before, but the DataSource property is available on all controls that work with lists, and the DataMember property is widely used to select a specific member property for display. My simple dialog built using this code is shown in Figure 6.

Figure 6
Figure 6: Simple Bound List

Master/Details Views

There is a second level of binding that is often carried out when displaying a Master list and that is displaying the Details for the currently selected item. We have the Master list, but how do we bind to the Details for the current selection?

It turns out that the solution is straightforward. In the previous example when we were binding simple controls to the BindingSource, what we were actually doing was binding to the currently selected entry in the list in the BindingSource. This is exactly what we want to do here – bind Details controls to the currently selected item.

So to make the port number for the currently selected item visible as a Label control, we make a binding on the Text property in the forms editor to the BindingSource we are using for the list, resulting in code like this:

label1.DataBindings.Add(
	new System.Windows.Forms.Binding(
	“Text”, bindingSourceServer,
	“Port”, true ) );
We can use the Master / Details view to edit items in the list too, by binding to the currently selected item to edit controls such as TextBox rather than static controls like Label.
textBox1.DataBindings.Add(
	new System.Windows.Forms.Binding(
	“Text”, bindingSourceServer,
	“Port”, true ) );
In addition to editing records, it is often a requirement that the user can add or remove records too. These operations should be handled through the BindingSource using the AddNew and RemoveCurrent methods, for example:
	private void buttonAdd_Click(
		object sender , EventArgs e )
	{
		bindingSourceServer.AddNew();
	}

	private void buttonRemove_Click(
		object sender , EventArgs e )
	{
bindingSourceServer.RemoveCurrent();
	}
If you wish have edit controls for multiple fields in a DataSet or multiple properties of a custom object, Visual Studio provides a shortcut for doing this. Open the Data Sources panel (“Show Data Sources” from the “Data” menu or Shift-Alt-D) and you will see the Server object appears there. The dropdown next to it allows you to define the type of control(s) that are generated if you drag it onto a form – by default the options will be a configured DataGridView or “Details”. If you select “Details” then when you drag the Server object and drop it on a form, controls will automatically be created for each data member – in fact you will end up with a dialog looking very much like that in Figure 1!

Navigating in Details View

Navigation, inserting and removing records are all standard operations, and BindingSource expresses through its properties which operations are valid. As a result, it is possible to use a standard piece of UI in the form of the BindingNavigator control for these operations. This can be used with a details only form, or with a form that has controls displaying the whole collection. To add a BindingNavigator, just drag the control from the toolbox onto your form, and set its BindingSource property to point to the relevant BindingSource object. The result is shown in Figure 7.

Figure 7
Figure 7: Adding a BindingNavigator

Internal Logic

The reason for using a custom object rather than a DataSet is usually to allow the custom object to encapsulate some piece of business logic. Very often, this logic causes the value of a number of properties to be linked. If setting one property (or even some completely external event like a file changing or a task completing) causes the value of the object properties to change in a way that the BindingSource could not reasonably anticipate, then we need to inform the BindingSource of these changes so it can update the UI.

The way we usually do this is by implementing INotifyPropertyChanged on the custom object. This interface has a single Event called PropertyChanged which should be fired (with the name of the changed property) whenever a property change occurs, even if it results directly from a set operation. If multiple properties change, then the event should fire once for each property. The BindingSource object will make use of the INotifyPropertyChange interface automatically if it is present, and will use the events to ensure that the UI is kept up to date.

Sorting, filtering and searching

Often an application needs to search a list, display a sorted list, or filter a list in some way to limit the quantity of data. The BindingSource object has properties and methods that support these operations. However, it does not implement them itself – it requires support from the data source. Searching and limited sorting functionality is available for data sources that implement IBindingList, and uses the Find method and the Sort property:
int index = bindingSourceAddress.Find(
	“Company” , “VSJ” );
bindingSourceAddress.Sort=”Name ASC”;
Filtering and multi-column sorting are available for data sources that implement IBindingListView (which inherits from IBindingList):
bindingSourceAddress.Filter= 
	“Country=’UK’”
bindingSourceAddress.Sort=
	“LastName ASC, FirstName ASC”;
Not all IBindingList and IBindingListView implementations support all of these features, so it is best to check the corresponding BindingSource.SupportsXxxxx property so you can avoid exceptions at runtime.

If you are using ADO.NET, the DataView class implements IBindingListView and therefore you will usually have access to full sorting, filtering and searching capabilities.

The collections in System.Collections such as the List<T> collection we used with our custom objects do not provide either of the interfaces required for searching, sorting or filtering. Nor are these particularly simple interfaces to implement. The framework does have a BindingList<T> generic, which implements IBindingList, but it doesn’t have any sorting or searching capability either, although inheriting from it would be a good way of providing a custom collection with these features. It should in principle be possible to provide generic filtering, sorting and searching of custom objects, perhaps using the Predicate delegate and the IComparer interface which are already used elsewhere. This would have been a useful addition to the framework and seems like a missed opportunity. Ultimately it will probably be easier to transfer data from a custom object into a DataTable than to implement IBindingList for most applications.

Conclusion

Although many will see the future of UI development lying with WPF, it will be a long time before WPF is widely deployed. In the meantime we should embrace anything that can make forms programming easier and less error prone. Data binding definitely falls into this category.


Ian Stevenson has been developing Windows software professionally for ten years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works as a consultant in the software industry, and can be contacted at [email protected].

You might also like...

Comments

About the author

Ian Stevenson United Kingdom

Ian Stevenson has been developing Windows software professionally for 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works a...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“XML is like violence - if it's not working for you, you're not using enough of it.”