To create effective data entry forms, you should do a few things to make your objects friendlier to the user interface controls that will bind to them. RIA Services entities automatically implement these features by default, but if you are binding to objects whose classes are defined on the client, such as a ViewModel when using the MVVM design pattern discussed in Chapter 13, or you're simply not using RIA Services at all, you will need to implement these features manually. Let's take a look at the most important features you should add to your objects for the benefit of the user interface.
One of the key interfaces that your objects should implement is the INotifyPropertyChanged
interface (found in the System.ComponentModel
namespace). This is a simple interface, requiring your class to implement a single event named PropertyChanged
. Bindings automatically listen for this event, and when it is raised, any binding associated with that property updates itself accordingly, and hence, the control associated with that binding is updated as well.
Note Silverlight 5 has introduced the INotifyPropertyChanging
interface, which you might like to implement in addition to the INotifyPropertyChanged
interface on your classes. However, here we'll focus only on the INotifyPropertyChanged
interface.
Once you've implemented the INotifyPropertyChanged
interface on your class, you can notify listeners when a property's value has changed by raising the PropertyChanged
event, passing it a PropertyChangedEventArgs
object containing the name of the property being updated. The following example demonstrates the code that you would use to notify the user interface that the value of a property named Name
has changed:
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
It's important to note that the PropertyChanged
event is never raised when you use automatically implemented properties. For example, properties implemented in the following fashion do not raise the PropertyChanged
event on the class, even when that class implements the INotifyPropertyChanged
interface:
public string Name { get; set; }
Therefore, the user interface will not be aware of any changes to the value of that property that it did not make itself and will not be updated when the underlying property value is changed. Unfortunately, this means that you must implement the getter and setter for the property and maintain the property's value in a member variable. You can then raise the PropertyChanged
event in the setter as required.
Note Some Visual Studio extensions enable you to easily turn an automatically implemented property into a property with a backing store (a member variable that maintains the property's value) and raise the PropertyChanged
event for that property. Choosing this option saves you from having to hand-code the getters and setters. Alternatively, try searching the Web for “INotifyPropertyChanged snippets”—you will find a multitude of snippets to help you create your properties accordingly and that you can customize to suit your needs.
The INotifyPropertyChanged
interface is very commonly used, and many developers add an OnPropertyChanged
(or similarly named) method that raises the PropertyChanged
event, rather than actually raising the event in the property setters. This simplifies the property setters, as you do not have to check that the PropertyChanged
event is not null,
which it will be if nothing is handling the event, before calling it.
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
In the property setter now, you can simply use a single line of code to raise the event:
OnPropertyChanged("Name");
This makes your property setters somewhat neater, although there is still the unfortunate requirement to pass the method the name of the property as a string. This requirement can result in situations where the property name is different from the string. For example, the string may misspell the property name, or the property name may have been refactored but the string was not updated accordingly. These issues can lead to particularly difficult bugs to identify and track down. In the following sections, we'll look at some other alternatives that can avoid these issues. Which approach you choose will come down to personal taste.
You can use reflection to get the name of the property in its setter (ignoring the first four characters, set_
) and pass that to the OnPropertyChanged
method:
OnPropertyChanged(MethodBase.GetCurrentMethod().Name.Substring(4));
Note For this code to compile, at the top of your file you will need a using
directive to the System.Reflection
namespace.
Alternatively, you may wish to continue with hard-coding the property name as a string and check in your OnPropertyChanged
method that the property exists. The best idea is to perform this check in a separate method and call that method from the OnPropertyChanged
method, as you can then decorate it with the Conditional
attribute and have it run only when in debug mode:
[Conditional("DEBUG")]
private void EnsurePropertyExists(string propertyName)
{
PropertyInfo propInfo = this.GetType().GetProperty(propertyName);
Debug.Assert(propInfo != null, "The property " + propertyName + " does not " +
"exist on this class");
}
Note For this method to work, you will need a using
directive to both the System.Reflection
namespace and the System.Diagnostics
namespace.
Yet another method is to use a lambda expression to specify the property name (instead of a string) and extract the name of the property from that. This popular method is faster than reflection and is refactoring safe (no magic strings). Start by adding the following using
directive to the top of your class:
using System.Linq.Expressions;
Now, define the following method in your class (or a base class):
private string GetPropertyName<T>(Expression<Func<T>> property)
{
MemberExpression expression = property.Body as MemberExpression;
return expression.Member.Name;
}
Note Generally, you would put the preceding method in the object's base class. Alternatively, you might like to define it as an extension method, as described by Jeremy Likeness at http://csharperimage.jeremylikness.com/2010/06/tips-and-tricks-for-inotifypropertychan.html
.
You can then call the OnPropertyChanged
method from your property's setter using the following code, where Name
is the name of the property:
OnPropertyChanged(GetPropertyName(() => Name));
Emiel Jongerius has written up some other alternatives, including another way of implementing this method that also sets the property's value, and how they compare performance-wise, at www.pochet.net/blog/2010/06/25/inotifypropertychanged-implementations-an-overview
. Einar Ingebrigtsen also has a blog post with an extended version of this code that provides additional functionality at www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx
.
Arguably the cleanest and simplest method of implementing property change notifications is using Notify Property Weaver. Notify Property Weaver is a Visual Studio extension created by Simon Cropp that removes the need for you to raise the PropertyChanged
event in property setters. Your classes simply need to implement the INotifyPropertyChanged
interface, and it handles the rest. It hooks into the compilation process, and “weaves” the required code into the application's IL code.
Benefits of this approach include the following:
Let's look at how you can use this tool.
INotifyPropertyChanged
interface, the tool will generate property changed notifications for each property in the class.More information on Notify Property Weaver can be found at the project's web site: http://code.google.com/p/notifypropertyweaver/.
Note Various other solutions have been created to solve the property change notifications problem that we won't go into here, but you might like to investigate. Oren Eini has another interesting method of handling the INotifyPropertyChanged
problem that you may like, where he has created an Observable<T>
class. He has blogged about it at http://ayende.com/Blog/archive/2009/08/08/an-easier-way-to-manage-inotifypropertychanged.aspx
. Justin Angel has yet another interesting method where he automatically implements INotifyPropertyChanged
using Mono.Cecil and PostSharp at justinangel.net/AutomagicallyImplementingINotifyPropertyChanged
.
IEditableObject
is another useful interface to implement in your objects (also found in the System.ComponentModel
namespace). It is designed to enable you to cancel and roll back any changes made to an object (essentially an undo action). Any changes made to the object are tracked using a start/complete-type transaction that can be cancelled. When a transaction is cancelled, any changes made to the object since the beginning of the transaction are rolled back, and the object's original state is reinstated.
Implementing the IEditableObject
interface requires your class to implement three methods: BeginEdit
, EndEdit
, and CancelEdit
. The BeginEdit
method should be called before any changes are made to the object, generally via a data entry form, and the EndEdit
method should be called once those changes have been made and are to be committed.
In the BeginEdit
method, you will need to write code to capture the state of the object at that time—that is, generally all the property values. The easiest way to implement this is using the MemberwiseClone
method on the object to take a shallow copy of the state of the object:
private Product _originalState = null; // Member variable
public void BeginEdit()
{
_originalState = this.MemberwiseClone() as Product;
}
You can then discard this state in the EndEdit
method, because any changes made to the object since the BeginEdit
method was called will have been accepted (committed):
public void EndEdit()
{
_originalState = null;
}
In the CancelEdit
method, you need to restore the original state of the object as it was taken when the BeginEdit
method was called, copying that state back to the current instance of the object. You can implement this method in one of two ways—either by manually assigning the values from the “backup” object or by using reflection.
Note The reflection method is more reusable because it takes a more generic approach, but it is slower to execute.
You can have full control over what property values are reinstated by assigning the values back to the object's properties manually, either via the property setters or directly to each member variable backing the corresponding property being reinstated. Note that if you use the property setters, each will execute any code within itself, including the property-changed notifications and validation rules—behavior you may or may not want.
public void CancelEdit()
{
if (_originalState != null)
{
Name = _originalState.Name;
ProductNumber = _originalState.ProductNumber;
// Etc...
_originalState = null;
}
}
Note If you choose to bypass the property setters, you may need to revalidate your object after reinstating the property values. (Validation is discussed later in this chapter.)
Alternatively, you can use a generic method that copies the values of all the public properties on the originalState
object back to the current instance using reflection:
public void CancelEdit()
{
if (_originalState != null)
{
PropertyInfo[] objectProperties =
this.GetType().GetProperties(BindingFlags.Public |
BindingFlags.Instance);
foreach (PropertyInfo propInfo in objectProperties)
{
object originalValue = propInfo.GetValue(_originalState, null);
propInfo.SetValue(this, originalValue, null);
}
_originalState = null;
}
}
Note The preceding method executes any code in the property setters.
Both the DataForm and DataGrid controls have built-in support for calling the BeginEdit
, EndEdit
, and CancelEdit
methods on bound objects that implement the IEditableObject
interface.
The DataForm control will call the BeginEdit
method when changes are first made to the bound object via the bound controls, and commit them by calling the EndEdit
method either when the user navigates away from the current object, or when the user explicitly commits the changes by clicking the OK button when the DataForm's AutoCommit
property is set to False
. As discussed earlier in this chapter, when a DataForm control is bound to an object that implements the IEditableObject
interface, a Cancel button will appear in the data entry form. Clicking this Cancel button will call the CancelEdit
method on the object to return it to its original state.
The DataGrid also calls the BeginEdit
method when changes are first made to a row and commits them by calling the EndEdit
method when the user navigates away from the current row. Pressing the Esc key when editing a row will call the CancelEdit
method on the object to return it to the state it was in prior to its being edited.
Often, you may wish to display a calculated value in your user interface based upon the values of properties on an object. For example, you may have an object entity representing an invoice line and want to display the total amount of that line (quantity × unit price). Alternatively, you may wish to have a property that displays the full name of a person, built by concatenating their first name and surname, separated by a space.
If you have created the class on the client side (such as a ViewModel), you can simply add a new property to the class that performs the calculation. Your user interface controls can then bind to this property and display the calculated value.
public decimal LineTotal
{
get
{
return Quantity * UnitPrice;
}
}
If you are binding directly to entities exposed from the server via RIA Services, you can add calculated properties to these entities too. You should never modify the code generated by RIA Services, but each of the generated entities is a partial class, so you can simply create a corresponding partial of your own (in the client-side project) that extends the entity and adds the required calculated properties in the same way as was previously demonstrated.
You may be wondering how you can notify the user interface that the LineTotal
property's value has changed, when the Quantity
or UnitPrice
properties are updated. If you shouldn't modify the code generated by RIA Services, where the Quantity
or UnitPrice
properties are defined, how can you notify the bindings that the value of the calculated property will have also changed? There are a number of options, but the best method is to extend one of the two partial methods that RIA Services has created for each property, which are called when their values are changing. For example, a UnitPrice
property on an entity will have two corresponding methods: OnUnitPriceChanging
(called before the value has changed) and OnUnitPriceChanged
(called after the value has changed). You can extend the Changed
method and notify the bindings of the property value change like so:
partial void OnListPriceChanged()
{
OnPropertyChanged(new PropertyChangedEventArgs("LineTotal"));
}