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(this, new 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 < string, object > ();
foreach (PropertyInfo pi in GetType().GetProperties())
{
if (pi.CanWrite)
_originalValues[pi.Name] = pi.GetValue(this, null);
}
_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

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

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!