2 August 2008

Windows Forms Data Binding

by mo


A couple of weeks ago, I was pairing on a new screen in a windows forms application. We decided to try a different way of binding domain objects to screen elements in our aplication.

The following is a method on the view that’s invoked from a presenter. It’s given an object from our model to display.

public void Display(IActionPlan actionPlan)
{
  Create
    .BindingFor(actionPlan)
    .BindToProperty(a => a.RecommendedAction)
    .BoundToControl(uxRecommendedAction);
  Create
    .BindingFor(actionPlan)
    .BindToProperty(a => a.AccountablePerson)
    .BoundToControl(uxAccoutablePerson);
  Create
    .BindingFor(actionPlan)
    .BindToProperty(a => a.EstimatedCompletionDate)
    .BoundToControl(uxEstimatedCompletionDate);
  Create
    .BindingFor(actionPlan)
    .BindToProperty(a => a.EstimatedStartDate)
    .BoundToControl(uxEstimatedStartDate);
  Create.BindingFor(actionPlan)
    .BindToProperty(a => a.RequiredResources)
    .BoundToControl(uxResourcesRequired);
  Create.BindingFor(actionPlan)
    .BindToProperty(a => a.Priority)
    .BoundToControl(uxPriority);
}

Each of our controls are prefixed with “ux”. What we did was bind different types of controls to property’s on the object to display. This immediately changed that state of the object as the user filled out information on the screen. The BindToPropery() method is given the property on the object to bind too. The following was the implementation we came up with.

  public static class Create
  {
    public static IBinding<T> BindingFor<T>(T object_to_bind_to)
    {
      return new ControlBinder<T>(object_to_bind_to);
    }
  }

  public interface IBinding<TypeToBindTo>
  {
    IBinder<TypeToBindTo> BindToProperty<T>(Expression<Func<TypeToBindTo, T>> property_to_bind_to);
  }

  public interface IBinder<TypeOfDomainObject>
  {
    string NameOfTheProperty { get; }
    TypeOfDomainObject InstanceToBindTo { get; }
  }

The implementation of the BindToProperty method takes in an input argument of type Expression<Func<TypeOfDomainObject>>. This allows us to inspect the expression to parse out the name of the property the binding is for. It’s like treating code as data. The IControlBinder implements two interfaces. One that’s issued to client components (IBinding) which restricts what they can do with the type. (see above in the Create class) The second interface exposes enough information for extension methods to pull from to build bindings for specific windows forms controls.

  public interface IControlBinder<TypeToBindTo> : IBinding<TypeToBindTo>, IBinder<TypeToBindTo>
  {
  }

  public class ControlBinder<TypeOfDomainObject> : IControlBinder<TypeOfDomainObject>
  {
    public ControlBinder(TypeOfDomainObject instance_to_bind_to)
    {
      InstanceToBindTo = instance_to_bind_to;
    }

    public IBinder<TypeOfDomainObject> BindToProperty<TypeOfPropertyToBindTo>(Expression<Func<TypeOfDomainObject, TypeOfPropertyToBindTo>> property_to_bind_to)
    {
      var expression = property_to_bind_to.Body as MemberExpression;
      NameOfTheProperty = expression.Member.Name;
      return this;
    }

    public string NameOfTheProperty { get; private set; }
    public TypeOfDomainObject InstanceToBindTo { get; private set; }
  }

The BoundToControl overloads were put into extension methods, allowing others to create new implementations of bindings without having to modify the Control binder itself.

public static class ControlBindingExtensions {
  public static IControlBinding BoundToControl<TypeOfDomainObject>(this IBinder<TypeOfDomainObject> binder, TextBox control) {
    var property_binder = new TextPropertyBinding<TypeOfDomainObject>(control, binder.NameOfTheProperty, binder.InstanceToBindTo);
    property_binder.Bind();
    return property_binder;
  }

  public static IControlBinding BoundToControl<T>(this IBinder<T> binder, RichTextBox box1) {
    var property_binder = new TextPropertyBinding<T>(box1, binder.NameOfTheProperty, binder.InstanceToBindTo);
    property_binder.Bind();
    return property_binder;
  }

  public static IControlBinding BoundToControl<T>(this IBinder<T> binder, ComboBox box1) {
    var property_binder = new ComboBoxBinding<T>(box1, binder.NameOfTheProperty, binder.InstanceToBindTo);
    property_binder.Bind();
    return property_binder;
  }

  public static IControlBinding BoundToControl<T>(this IBinder<T> binder, DateTimePicker box1) {
      var property_binder = new DatePickerBinding<T>(box1, binder.NameOfTheProperty, binder.InstanceToBindTo);
      property_binder.Bind();
      return property_binder;
  }
}

For completeness… the control bindings…

  public class TextPropertyBinding<TypeToBindTo> : IControlBinding {
    private readonly Control control_to_bind_to;
    private readonly string name_of_the_propery_to_bind;
    private readonly TypeToBindTo instance_of_the_object_to_bind_to;

    public TextPropertyBinding(Control control_to_bind_to, string name_of_the_propery_to_bind, TypeToBindTo instance_of_the_object_to_bind_to) {
      this.control_to_bind_to = control_to_bind_to;
      this.name_of_the_propery_to_bind = name_of_the_propery_to_bind;
      this.instance_of_the_object_to_bind_to = instance_of_the_object_to_bind_to;
    }

    public void Bind() {
      control_to_bind_to.DataBindings.Clear();
      control_to_bind_to.DataBindings.Add("Text", instance_of_the_object_to_bind_to, name_of_the_propery_to_bind);
    }
  }

  public class ComboBoxBinding<TypeToBindTo> : IControlBinding {
    private readonly ComboBox control_to_bind_to;
    private readonly string name_of_the_propery_to_bind;
    private readonly TypeToBindTo instance_of_the_object_to_bind_to;

    public ComboBoxBinding(ComboBox control_to_bind_to, string name_of_the_propery_to_bind, TypeToBindTo instance_of_the_object_to_bind_to) {
      this.control_to_bind_to = control_to_bind_to;
      this.name_of_the_propery_to_bind = name_of_the_propery_to_bind;
      this.instance_of_the_object_to_bind_to = instance_of_the_object_to_bind_to;
    }

    public void Bind() {
      control_to_bind_to.SelectedIndexChanged += delegate {
        typeof (TypeToBindTo)
          .GetProperty(name_of_the_propery_to_bind)
          .SetValue(instance_of_the_object_to_bind_to, control_to_bind_to.Items[control_to_bind_to.SelectedIndex], null);
      };
    }
  }

  public class DatePickerBinding<TypeToBindTo> : IControlBinding {
    private readonly DateTimePicker control_to_bind_to;
    private readonly string name_of_the_propery_to_bind;
    private readonly TypeToBindTo instance_of_the_object_to_bind_to;

    public DatePickerBinding(DateTimePicker control_to_bind_to, string name_of_the_propery_to_bind, TypeToBindTo instance_of_the_object_to_bind_to) {
      this.control_to_bind_to = control_to_bind_to;
      this.name_of_the_propery_to_bind = name_of_the_propery_to_bind;
      this.instance_of_the_object_to_bind_to = instance_of_the_object_to_bind_to;
    }

    public void Bind() {
      control_to_bind_to.DataBindings.Clear();
      control_to_bind_to.DataBindings.Add("Value", instance_of_the_object_to_bind_to, name_of_the_propery_to_bind);
    }
  }

We found that using the fluent interface for creating bindings was pretty easy and made screen synchronization a breeze, however, our implementation wasn’t the easiest thing to test. So far it’s been good to us.

csharp oop