Great Features for MVVM Friendly Objects Part 1 - Introducing Property Change Behaviors

by Administrator 20. June 2010 20:23

In the next article in this series, I introduce the idea of Property Change Behaviors.  This concept is used to provide a means of easily attaching useful features to data objects.  Any number of features can be added without requiring a complex inheritance structures or keeping you from configuring the features on a per-property or per-instance basis.

This article is part of a series about useful MVVM friendly features for your data objects and favoring composition over inheritance.  You can read the other parts:

Can we all agree to call Silverlight/WPF development “XamlWorld” ?

Introducing Property Change Behaviors

Recall that we opened the first article with a list of actions we might want to take when a property on a XAMLWorld data object changes:

  1. Firing change notification
  2. Handling property-level validation
  3. Tracking when an object has changed (dirty/change tracking)
  4. Update dependent or calculated properties
  5. Providing Undo support

It would be ideal if we could provide a mechanism to do all of these things, and whatever else we think of in the future, in a generic fashion.  Since XAML technologies provide fantastic data binding support centered around the notion of CLR Properties, connecting our behaviors to properties is a very good approach.  If you want to use anything that’s built into XamlWorld, you need to make peace with the notion that The Property is the coin of the realm. 

We can think of Property Changed Behaviors as a sort of “visitor” or “Chain of Responsibility”.  Here is the interface for Property Change Behaviors:

public interface IPropertyChangedBehavior
{
    /// <summary>
    /// Do something useful when a property changes
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="owningInstance">Object instance whose property has changed</param>
    /// <param name="oldVal">current property value</param>
    /// <param name="newVal">new property value</param>
    /// <param name="propertyName"></param>
    /// <returns>Returns true of more behaviors can keep processing</returns>
    bool PropertyChanged<T>(object owningInstance, T oldVal, T newVal, string propertyName);
}

There are more complex scenarios for IPropertyChangedBehavior, but for this series of articles we’ll stick with the first draft.  This interface allows us to do interesting things when Property values change.

Remember what our BindableType property setter looked like previously?  Recall that we did this to avoid re-creating the basic INotifyPropertyChanged implementation over and over:

protected void OnPropertyChanged(string propName)
{
    if (null != PropertyChanged && !SuspendChangeNotification)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
}

In order to move forward I’ve changed the semantics somewhat.  The code snippet for setting properties in this style is provided at the end of this article.  Keeping with the Metro BBQ example from the previous article, here’s what the properties look like on my BBQGrill class.

Materials _KettleMaterial;

public Materials KettleMaterial
{
    get { return _KettleMaterial; }
    set { Set<Materials>(ref _KettleMaterial, value, "KettleMaterial"); }
}

This allows me to use semantics like the following on the BindableType base class.  Yes, we’re still extending BindableType for now, one step at a time.  We will eventually refactor to avoid the need to extend BindableType.

protected void Set<T>(ref T local, T newVal, string name)
{            
    T localCopy = local;
    local = newVal;

    if (null != _changeBehaviors)
    {                
        foreach (var behavior in _changeBehaviors)
        {
            bool @continue = behavior.PropertyChanged<T>(this, localCopy, newVal, name);
            if (!@continue) { break; }
        }
    }
    else//no behaviors, just carry on with the old way
    {
        OnPropertyChanged(name);
    }                
}

Now things are starting to get interesting.  BindableType has an instance variable to contain the IPropertyChangedBehaviors we want to use.

protected IEnumerable<IPropertyChangedBehavior> _changeBehaviors;

Each object now has a configurable list of interesting actions that we can go through any time a property value changes.  Based on the fact that we’d like to enable Binding updates, the choice for the first IPropertyChangedBehavior implementation is obvious:

public class NotifyPropertyBehavior : IPropertyChangedBehavior
{
    public NotifyPropertyBehavior(INotifyPropertyChanged target, Action<string> changedCallback)
    {
        Requires.NotNull(target, "target cannot be null");
        Requires.NotNull(changedCallback, "changedCallback cannot be null");
        Target = target;
        ChangeCallback = changedCallback;
    }

    public INotifyPropertyChanged Target { get; set; }

    /// <summary>
    /// Needed since only the owning instance can fire the event
    /// </summary>
    public Action<string> ChangeCallback { get; set; }


    /// <summary>
    /// Fire change notification only if the new value != old
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="owningInstance"></param>
    /// <param name="oldVal"></param>
    /// <param name="newVal"></param>
    /// <param name="propertyName"></param>
    /// <returns></returns>
    public bool PropertyChanged<T>(object owningInstance, T oldVal, T newVal, string propertyName)
    {
        if (!Object.Equals(oldVal, newVal))
        {
            ChangeCallback(propertyName);
            return true;
        }
        else
        {
            return false;
        }
    }
}

… and in the constructor of BindableType, we create the following defaults used in the Setter, above:

_changeBehaviors = new List<IPropertyChangedBehavior>

{ new NotifyPropertyBehavior(this, s=> OnPropertyChanged(s) ) };

Now, we are in a situation where our data objects can have any number of IPropertyChangedBehaviors attached.  For now, the NotifyPropertyBehavior has merely replaced how we fire change notification, but the behaviors shown in the following articles will implement much more interesting functionality.

PropertyChangedBehaviorCD

Conclusion

So, we’ve created some basic functionality for iterating through an ordered list of interesting actions whenever a property value changes.  Based on the logic inside BindableType, if the property values are equal we would stop processing subsequent Property Change Behaviors for this instance by returning false from NotifyPropertyBehavior.

In the next article we’ll start adding more useful and interesting features using the IPropertyChangedBehavior approach.

Download the bprop code snippet.

Tags:

Comments (2) -

dob
dob
6/21/2010 1:00:01 PM #

Thanks for sharing this. I have a few questions if you don't mind...
The order of foreach (var behavior in _changeBehaviors) may be significant. For a List<IPropertyChangedBehavior> impl the order will be in the order they were added i.e. 0,1,2...

If I wanted to record the fact that someone entered the same value as the original (a kind of update to say I am confirming the value is still ok but not changing the value) or another example would be revalidating that the entered value is still ok by comparing to a stored value retrieved via an async service call. These scenarios may not be in your scope but I'll ask anyway. How would you suggest handling these scenarios? Ensure the order they are added is appropriate? Provide a means to change the order? Make IPropertyChangedBehavior.PropertyChanged return something other than a bool?

Reply

Damon
Damon
6/21/2010 2:12:11 PM #

dob,
I have changed _changeBehaviors to List<> in future articles already written.  The "going back to original value" scenario is also covered in the future using two different techniques.  

Reply

Pingbacks and trackbacks (4)+

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


About the author

Damon Payne is a Microsoft MVP specializing in Smart Client solution architecture. 

INETA Community Speakers Program

Month List

Page List

flickr photostream