Growing an MVVM Framework in 2003, part II—Properties

This is second post in a series on my experiences starting to grow an MVVM Framework in .NET 1.1.

* Part I—Event Handlers * Part II—Properties * Part III —Properties Redux * Part IV—Unit Tests * Part V—Reflections and Regrets Full source code can be found in my Google Code repository.

Last time, I introduced a tiny Windows Forms application and described my efforts to make a small MVVM framework for it. At the end of that post, we'd seen one way to use convention to bind View events to ViewModel event handlers.

Today I'll talk about properties. It's all very well to have a click on the "Find" button trigger the FindClick method on the ViewModel, but it's useless unless we know what to look for. I needed a way to pass the Title.Text value to the ViewModel so it could use it for the search. Then the FindClick method I showed last time would work:

public void FindClick(object sender, EventArgs e)
{
    ICollection books = bookDepository.Find(TitleText);
    BookListItems.Clear();
    foreach ( string book in books )
    {
        BookListItems.Add(book);
    }
}

A Failed Attempt

First I tried using Windows Forms binding, with lamentable results. I wish I'd saved the intermediate steps, as I was probably doing something wrong and could've solicited help. Still, whether it was due to a lack of experience on my part, or a flaw in the system, the bindings just wouldn't work. I could bind bools and strings, but lists were right out.

A Proxy for Properties

I decided to rely on the storage objects that came with the View elements. This meant the ViewModel needed some way to proxy the properties on the View. Then a get or a set on the ViewModel object would flow right through, reading or writing the View's values. Here's what I came up with:

public class BoundProperty: Property
{
    private object obj;
    private PropertyInfo propertyInfo;

    public BoundProperty(object obj, PropertyInfo property)
    {
        this.obj = obj;
        this.propertyInfo = property;
    }

    public override object Value
    {
        get { return propertyInfo.GetValue(obj, null); }
        set { propertyInfo.SetValue(obj, value, null); }
    }
}

Ignore the Property base class for a bit. An instances p of type BoundProperty can be used to get and set values on the proxied object obj like so:

p.Value = valueA;
object valueB = p.Value;

Not incredibly thrilling, but one can work with it. Using the .Value in order to access the value was a little cumbersome, so I added a little syntactic sugar in the Property base class:

public abstract class Property
{
    public abstract object Value { get; set; }

    public static implicit operator string(Property prop)
    {
        return (string) prop.Value;
    }

    public static implicit operator bool(Property prop)
    {
        return (bool) prop.Value;
    }

    public IList AsList()
    {
         return (IList) Value;
    } 
}

I really like the implicit operator functionality, which I'd never used before. I wish it could be used with interfaces, though. There's probably a good reason why it can't, but nothing comes to mind. Anyhow, I had to go another route for IList—the somewhat uninspiring AsList method. At this point, I was really missing generics.

Still, it's nicer to be able to write

string myString = p1;
IList myList = p2.AsList();

instead of

string myString = (string) p1.Value;
IList myList = (IList) p2.Value;

Hooking up the Properties

This is pretty much the same as hooking up the events like the last time. All we have to do is define a field (yes, a field) of type Property in the ViewModel:

private Property titleText;

The ViewModelBase loops over all the Property fields and looks for View controls that have matching property names:

foreach ( FieldInfo field in PropertyFields() )
{
    FindPropertyToBindTo(allControls, field);
}

private void FindPropertyToBindTo(ArrayList allControls, FieldInfo field)
{
    foreach ( Control control in allControls )
    {
        if ( BindFieldToControl(control, field) ) { return; }
    }
}

private bool BindFieldToControl(Control control, FieldInfo field)
{
    string controlPropertyName = ControlAttributeName(control, field);
    if ( controlPropertyName == null ) { return false; }

    PropertyInfo controlProperty = control.GetType().GetProperty(controlPropertyName, myBindingFlags);
    if ( controlProperty != null )
    {
        field.SetValue(this, new BoundProperty(control, controlProperty));
    }
    return true;
}

Technically that's it, but the rest of the ViewModel's code is a little cleaner if we self encapsulate the field:

public string TitleText
{
    get { return titleText; }
    set { titleText.Value = value; }
}

Remarks

Once the infrastructure was in place, I really started enjoying developing the application. It was very liberating to add a new event handler just by writing a method with the right name and signature. And even adding access to a new property wasn't so bad—writing the three lines of code to segregate the conversions and .Values was worth it to keep the event handler bodies nice and clean.

Next time, we'll see how the design affected the form of the application's unit tests.