AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1346&pId=-1
Getting the Most Out of Windows Forms Data Binding
page
by Jon Kruger
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 24687/ 52

Introduction

Data binding is a great way to work with data and UI controls, but there is a lot more to it than just setting the DataSource property of a grid. The .NET Framework provides interfaces and components that will let the framework do most of the work for you — as long as you set it up right.

N-Tier Design

First of all, we need to remember that a good architecture will keep as much functionality in the business objects as possible. In order to implement the interfaces that you need for data binding, you will have to put certain functionality in the business objects, such as validation, error messages, dirty state, and undoing edits to the object.

Even if you are not using data binding, it is generally good practice to design your object this way anyways. This way, if you decide later on to rip off your WinForms UI and replace it with a web front end, the transition should (hopefully) be seamless.

Now, let us take a look at some of the important interfaces that you will want to implement when using data binding.

Interfaces

INotifyPropertyChanged

The INotifyPropertyChanged interface is used to notify listeners that a property on the object has changed. When a property’s value changes, you will fire the PropertyChanged event; I do this in the example below in the NotifyPropertyChanged() method.

Your data binding may seem to work fine without implementing this interface, but that is because the data in your object is probably only changing when the user types in a new value to a control. What happens when something in the business object changes for some another reason?

For example, in the code sample below when a property changes, we are going to update the value in the LastUpdate property.

Listing 1

public class Person: INotifyPropertyChanged
{
  private string _firstName;
  private string _lastName;
  private string _email;
  private DateTime _lastUpdate;
 
  public string FirstName
  {
    get
    {
      return _firstName;
    }
    set
    {
      if (_firstName == value)
        return ;
 
      _firstName = value;
      NotifyPropertyChanged("FirstName");
    }
  }
 
  public string LastName
  {
    get
    {
      return _lastName;
    }
    set
    {
      if (_lastName == value)
        return ;
 
      _lastName = value;
      NotifyPropertyChanged("LastName");
    }
  }
 
  public string Email
  {
    get
    {
      return _email;
    }
    set
    {
      if (_email == value)
        return ;
 
      _email = value;
      NotifyPropertyChanged("Email");
    }
  }
 
  public DateTime LastUpdate
  {
    get
    {
      return _lastUpdate;
    }
  }
 
  public event PropertyChangedEventHandler PropertyChanged;
 
  protected void NotifyPropertyChanged(string propertyName)
  {
    if (propertyName != "LastUpdate")
    {
      _lastUpdate = DateTime.Now;
      NotifyPropertyChanged("LastUpdate");
    }
 
    if (PropertyChanged != null)
      PropertyChanged(thisnew PropertyChangedEventArgs(propertyName));
  }
}

Because we implemented INotifyPropertyChanged, a grid that is bound to this object will get the updated value of LastUpdate when one of the other properties changes.

IDataErrorInfo           

The IDataErrorInfo interface provides a way for your business object to provide error information to listeners, such as a grid or an ErrorProvider component.

Figure 1

The IDataErrorInfo interface has two properties — an Error property that returns an error message for the entire object and an indexer that returns an error message for a specific property.

I am not exactly sure why Microsoft chose to use an indexer in this interface. When you see code that says, “MessageBox.Show(person[”Email”]),” you would not naturally think that you would be displaying an error message for the Email property. In my opinion, something like GetError("Email") would have been more fitting.

But all opinions aside, your best choice is probably to implement this interface explicitly. By doing this, you will have to cast the object to an IDataErrorInfo before you can access the indexer.

Listing 2

Person person = new Person();
// If you implement IDataErrorInfo explicitly, this will not compile
MessageBox.Show(person["Email"]);
// If you implement IDataErrorInfo explicitly, you have to cast
// the Person object to IDataErrorInfo in order to access the
// indexer.  This allows you to have an indexer on your object
// for some other reason.
MessageBox.Show(((IDataErrorInfo) person)["Email"]);

IBindingList

When you bind a control such as a grid to a list, you should bind to a collection that implements IBindingList. The IBindingList interface contains methods and properties that deal with sorting of the list and the adding and deleting of items from the list. You can use the System.ComponentModel.BindingList class in most cases, so you do not necessarily have to know about all of the nitty gritty details of implementing IBindingList. The important thing to know is that a grid that is bound to an IBindingList will automatically update when items are added or removed from the underlying list.

When you want to add a new row to a data bound grid, you will have to do so in the underlying list by calling IBindingList methods.

Listing 3

// This line will throw an exception because you cannot
// call this method when the grid is data bound -- you
// have to add the item to the underlying list.
//this.dataGridView.Rows.Add();
// This is the correct way.
Person newPerson = _list.AddNew();

If you want to remove an item from the list or the grid, you can do it either by removing the object from the list or by telling the grid to remove the row.

Listing 4

// Either one of the methods below will work for
// removing the row from the list and the grid.
 
if (/* remove from the underlying list */)
{
    // Remove the item from the underlying list.
    _list.Remove((Person)row.DataBoundItem);
}
else
{
    // Tell the grid to remove the row
    this.dataGridView.Rows.Remove(row);
}

IEditableObject

The IEditableObject interface provides functionality to commit or rollback changes to an object that is used as a data source. The primary application of this is when you are editing a record in a grid and you want to click a Cancel button and undo all of the edits that you made to that record.

Implementing IEditableObject is fairly straightforward. There are three methods: BeginEdit(), CancelEdit(), and EndEdit(). In BeginEdit(), flag the object as being in edit mode and store off all of the values in the object. In CancelEdit(), clear the edit mode flag and restore all of the object values that you stored in BeginEdit(). In EndEdit(), clear the edit mode flag, forget all of the values that you stored in BeginEdit() and, if necessary, commit the changes to the object to your data store.

Here is the tricky part. If your object is bound to a grid, for example, the grid will call BeginEdit() anytime you edit any part of the record. So in your BeginEdit() method, you will want to keep track of whether the object is already in edit mode so that you do not go storing off values every time a cell is edited.

Listing 5

public void BeginEdit()
{
  if (!_doingEdit)
  {
    _originalValues = new Dictionary < stringobject > ();
    foreach (PropertyInfo pi in GetType().GetProperties())
    {
      if (pi.CanWrite)
        _originalValues[pi.Name] = pi.GetValue(thisnull);
    }
    _doingEdit = true;
  }
}

If you are editing a record in a grid, for example, and you select a different row, the grid will call EndEdit() for you. You can also call EndEdit() and CancelEdit() yourself (for example, when a Save or Cancel button is clicked).

The BindingSource component — tying it all together

In .NET 2.0 the BindingSource component was introduced. The BindingSource acts as a layer of abstraction between your control and your actual data source. In the designer you can set the DataSource property of the BindingSource to an object type, which will provide access to type information in the designer so that you can set up almost all of your data binding in the designer.

Figure 2

Setting the DataSource property of the BindingSource

Now if you select your BindingSource component as your grid’s DataSource, all of the columns will be added to the grid and you will be able to work with the columns at design-time.

Another benefit of the BindingSource component is that it will allow you to choose from a list of your object’s properties when you want to bind individual controls.

Figure 3

 

Setting data bindings on a textbox

 

Not only that, but if your form contains an ErrorProvider component on your form, you can set the DataSource of the ErrorProvider to the BindingSource. And if you have implemented IDataErrorInfo on your business object, the ErrorProvider will automatically display error messages in your form!

Suggested Readings

There are even more interfaces that are used in data binding. My goal here was to talk about the ones that I find I have to use the most.

If you got scared off by data binding in .NET 1.x, take heart, it’s much better now than it used to be!

For a more in-depth discussion on data binding, including stuff like the BindingContext and CurrencyManager, go here.

Now that you know how data binding works, you get to relearn everything in WPF because it is all changed. If you are venturing into WPF-land, you might want to go here.

Downloads
Conclusion

In this article you have learned some of the important interfaces which you will use for data binding with the help of implementation examples.



©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-28 7:39:20 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search