Windows Forms Data Binding


A couple of weeks ago, Adam and I were pairing on a new screen in a windows forms application. He started showing me some stuff that he had learned about windows forms data bindings. I showed him a little bit of what JP tried to teach me, back in the Austin Nothin’ But .NET boot camp, about Expressions and we decided to try a different way of binding domain object to screen elements in our application. 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>. 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. The extension methods….

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.

As a side note… go register for the Las Vegas course, it may cause you to love your job! Also, if you’ve already attended a boot camp, and you think you already know what the course is about, you have no idea, it keeps getting better and better.

Recursive Command