Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Observable : Automatically Notify Property Owner When a Property is Changed

0.00/5 (No votes)
5 Jun 2010 1  
Reduce the property declaration code to one line, yet still usable directly.

Introduction

Ivan Krivyakov suggested an Observable<T>, when declared as property in a class, and the property is changed, it will raise a PropertyChanged event automatically, thus any WPF control that links to the Observable<T> will update the value.

I found it useful because a developer no longer needs to write getter, setter code and notifyChange code for each property, which can reduce a lot of copy & paste work. So I created the version 2 of Observable<T>. It works, but it has a limitation, as Ivan Krivyakov said:

It is nice indeed, but the users of this will have this pesky “Value” always getting in the way, won’t they?

C#
{Binding MyModel.MyProperty.Value}
// C#
if (ThisProperty.Value > ThatProperty.Value)
{
return A.Value + B.Value * C.Value;
}

That's the reason for the more complicated version 3.

How to Use?

You can set the value using both its Observable<T>.Value property or Observable<T> itself.

C#
public class Person : ModelBase
{
   public Observable<string> LastName { get { return getVar<string>("LastName"); } }
   public Observable<int> Age { get 
	{ return getVar<int>("Age"); } set { setVar<int>("Age", value); } }
}
...
Person p = new Person();
private void Button_Click(object sender, RoutedEventArgs e)
{
   p.LastName.Value += "QuickZip"; //Set using Obsersable<T>'s Value property
   p.Age += 1; //Set directly!
}

XAML

XML
<TextBlock Text="{Binding LastName}" /> <!-- Both way works -->
<TextBlock Text="{Binding Age.Value}" />

How it Works?

In version 2, when Observable<T>.Value is changed, it will trigger Observable<T>’s NotifyChanged() method, thus notify the listener, but as it doesn’t affect its parent (ModelBase), so if a WPF Control is bound to the Observable<T> directly instead of its Value property, it cannot be notified for any changes.

So how does version3 support that?

C#
public Observable<int> Age { get { return getVar<int>("Age"); } 
	set { setVar<int>("Age", value); } }
protected void setVar<T>(string name, Observable<T> value)
{
    ...
    variableDic.Add(name, new Observable<T>(new Action(() => 
			{ NotifyPropertyChanged(name); })));
    ((Observable<T>)variableDic[name]).Value = value.Value;
}

First, setter now can be implemented, like Age in this sample, when set it calls setVar() method, which updates Age.Value. As you see, the new Observable<T> now includes a method to callback, so when its Value is changed, it will call both its and its owner’s NotifyPropertyChanged() method.

But that's not sufficient to make it usable directly, nobody wants to call the following just to set a new value.

C#
Age = new Observable<int>(null) { Value = 11 }

So for XAML access, a TypeConverter is written to “Extract” Observable<T>.Value property.

C#
public class ObservableConverter : TypeConverter
{
  public override object ConvertTo(ITypeDescriptorContext context, 
	CultureInfo culture, object value, Type destinationType)
  {
    object valueValue = value.GetType().GetProperty("Value").GetValue(value, null);
    return base.ConvertTo(context, culture, valueValue, destinationType);
  }
}

For setting from CS code, I overrode a number of operators, including the assignment (implicit, + and -) operator. I used Jon Skeet’s implementation to perform Addition and Subtraction operations to generic operator, if you need you can implement other operators, you can use a similar way to do it, more information about operator can be found in MSDN.

C#
public static implicit operator Observable<T>(T value)
{
    return new Observable<T>(null) { Value = value };
}

public static Observable<T> operator +(Observable<T> value1, Observable<T> value2)
{
    return new Observable<T>(null) { Value = Tools.Add(value1.Value, value2.Value) };
}

Implementation

C#
public class ModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> variableDic = new Dictionary<string, object>();

    protected Observable<T> getVar<T>(string name) //GetVariable
    {
        if (!variableDic.ContainsKey(name))
            variableDic.Add(name, new Observable<T>
		(new Action(() => { NotifyPropertyChanged(name); })));
        return (Observable<T>)variableDic[name];
    }

    protected void setVar<T>(string name, Observable<T> value)
    {
        if (variableDic.ContainsKey(name))
            variableDic.Remove(name);
        variableDic.Add(name, new Observable<T>(new Action(() => 
				{ NotifyPropertyChanged(name); })));
        ((Observable<T>)variableDic[name]).Value = value.Value;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(string property)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property));
    }
}

public class Person : ModelBase
{
    public Observable<string> LastName { get { return getVar<string>("LastName"); } }
    public Observable<int> Age { get { return getVar<int>("Age"); } 
		set { setVar<int>("Age", value); } }
}
public class ObservableConverter : TypeConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, 
		CultureInfo culture, object value, Type destinationType)
    {
        object valueValue = value.GetType().GetProperty("Value").GetValue(value, null);
        return base.ConvertTo(context, culture, valueValue, destinationType);
    }
}

public static class Tools
{
    //http://www.yoda.arachsys.com/csharp/genericoperators.html
    public static T Add<T>(T a, T b)
    {
        // declare the parameters
        ParameterExpression paramA = 
		System.Linq.Expressions.Expression.Parameter(typeof(T), "a"),
            paramB = System.Linq.Expressions.Expression.Parameter(typeof(T), "b");
        // add the parameters together
        BinaryExpression body = System.Linq.Expressions.Expression.Add(paramA, paramB);
        // compile it
        Func<T, T, T> add = System.Linq.Expressions.Expression.Lambda<Func<T, T, T>>
				(body, paramA, paramB).Compile();
        // call it
        return add(a, b);
    }

    public static T Subtract<T>(T a, T b)
    {
        // declare the parameters
        ParameterExpression paramA = 
		System.Linq.Expressions.Expression.Parameter(typeof(T), "a"),
            paramB = System.Linq.Expressions.Expression.Parameter(typeof(T), "b");
        // add the parameters together
        BinaryExpression body = System.Linq.Expressions.Expression.Subtract
				(paramA, paramB);
        // compile it
        Func<T, T, T> subtract = System.Linq.Expressions.Expression.Lambda
				<Func<T, T, T>>(body, paramA, paramB).Compile();
        // call it
        return subtract(a, b);
    }
}

[TypeConverter(typeof(ObservableConverter))]
public class Observable<T> : INotifyPropertyChanged
{
    T _value;
    Action _updateAction;

    public Observable(Action updateAction)
    {
        _updateAction = updateAction;
    }

    public T Value
    {
        get { return _value; }
        set
        {
            if (value == null || !value.Equals(_value))
            {
                _value = value;
                NotifyPropertyChanged("Value");
                if (_updateAction != null)
                    _updateAction.Invoke();
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string property)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public static implicit operator Observable<T>(T value)
    {
        return new Observable<T>(null) { Value = value };
    }

    public static Observable<T> operator +(Observable<T> value1, Observable<T> value2)
    {
        return new Observable<T>(null) { Value = Tools.Add(value1.Value,value2.Value) };
    }

    public static Observable<T> operator -(Observable<T> value1, Observable<T> value2)
    {
        return new Observable<T>(null) 
	{ Value = Tools.Subtract(value1.Value, value2.Value) };
    }
}
}

This article has been posted on . You can find a list of my articles here.


License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here