Tuesday, September 04, 2007

Model View Presenter Data Binding Part 1: The View

When I first started using Windows Forms, I immediately realized that it doesn't scale unless you put forth some effort and discipline in building some infrastructure. The built-in data binding is dodgy unless you just use the Forms Designer to do everything in Visual Studio. There's no definitive documentation on how you can do data binding on something that's not a DataSet or DataTable. There are some unofficial guidelines (can't find the link now, but will post when I do), but nothing that really serves as a reference. (If there is some documentation... then I guess I just didn't look deep enough on the Internet). I went down this route because at work I was trying to bind a POCO (Plain Old CLR Object) to a third-party grid (http://xceed.com/Grid_Winforms_Intro.html). Luckily, the documentation that Xceed distributes with their product pointed me in the right direction and gave me some sample code as a start. The key is IBindingList, which has its own manual of rules. I got a little bit of a start and managed to create a base class to make life as easy as possible. One issue to note is that I'm still using .NET Framework 1.1, which doesn't have generics. .NET 2.0 has a generic BindingListBase type that will work a lot better than my simplified approach. The nice thing is that I'll be ready for the jump to 2.0 since I'm already using this brand of collection. The other nicety that .NET 2.0 provides is INotifyPropertyChanged. .NET 1.1 has the delegate type (PropertyChangeEventHandler) and the EventArgs type (PropertyChangeEventArgs), but is just missing the interface to tie it all together. Again, I'm thinking ahead and I'll be ready for the jump to 2.0. Back to Forms data binding: Windows Forms has a really extensive set of APIs and helpers and types (oh my) to bind controls to DataSets which are, in turn, tied to databases directly using ADO.NET, but doesn't play as well with POCOs that are common with having a business layer or with ORM scenarios. I tried to get it to work... but there was always something missing. To make life easier, I created my own set of binding methods based partly on this CodeProject article (http://www.codeproject.com/csharp/simpledatabinding.asp). The article describes a simpler binding mechanism that is able to bind any property to any other property. It tried to take advantage of IConvertible to change types that are compatible but not exactly the same. This approach is super easy because custom types can just implement IConvertible and they'll "just work" with the binding. Also, if you bind properties of the same type, then you don't even need IConvertible, it will just assign the value. I added some extra capabilities to the binding and allowed it to take a reference to an ISynchronizeInvoke object that will make sure that the synchronizing code is executed on the right thread when dealing with UI objects. The basic syntax is as follows: ThreadAwareBinding.Bind(myDataObject, "Property1", myTextField, "Text", myTextField); The really neat thing (and this is from the article) is that at bind-time, the binder will examine both properties and look for an event named "Changed", where PropertyName is the property that is bound. In my version, I allow the user to specify a trigger event explicitly, or it will look for my version of the INotifyPropertyChanged interface. Whichever it finds first, it will use that mechanism for binding. If more than one is provided, it will prioritize. On the data objects, I specify properties with the following pattern:
int _nThingy;
public int Thingy
{
    get
    {
        return _nThingy;
    }
    set
    {
        bool bChanged = (this.Thingy != value);
        _nThingy = value;
        if (bChanged) FireDataChanged("Thingy");
    }
}
I made a macro in Visual Studio so that all I had to do was type the name of the property and it would be expanded into this template. So, for the "View" aspect of the Model-View-Presenter, I do the following things:
  1. Create a Form or UserControl called "MyView".
  2. Create an interface called "IMyView" that has all the properties declared that we want to display on the control / form.
  3. Implement the "IMyView" interface on the form or user control, also implement the INotifyPropertyChanged interface.
  4. In the Load event, bind the properties to the desired controls (I have automated this step somewhat, I'll talk about it in another post).
The end result is that the View can be modified through its interface properties and can be swapped out for a mock object or something else if we want to do some testing.