3
\$\begingroup\$

Below is the code of a custom C# class I have called UniversalTextBoxTextBinder. I'm interested to hear the code review comments to find out the limitations of this 'universal' solution as well as to get posted here the references on similar solutions. Obviously the solution posted here can be generalized on other types of WinForms controls, WPF(?) controls etc.

NB: This solution would not see the light without Hans Passant hint:

/// <summary>
/// Dynamically binds 'two-way' the properties' values of a (POCO) instance of <paramref name="T"/> 
/// to the host control textboxes' 'Text' property.
/// </summary>
/// <typeparam name="T"></typeparam>
public class UniversalTextBoxTextBinder<T>
{
        public UniversalTextBoxTextBinder(
            Control hostControl, 
            T boundObject,
            string textBoxNamePrefix = default(string), 
            string textBoxNameSuffix = "TextBox"
        )
    {
        _hostControl = hostControl; 
        this.BoundObject = boundObject;
        _expandoObject = new ExpandoObject();

        IDictionary<string, object> dictionary = _expandoObject as IDictionary<string, object>;

        foreach (PropertyInfo propertyInfo in this.BoundObject.GetType().GetProperties())
        {
            if (propertyInfo.CanRead && propertyInfo.CanWrite)
            {
                dictionary[propertyInfo.Name] = propertyInfo.GetValue(this.BoundObject);
                bind(propertyInfo); 
            }
        }

        ((INotifyPropertyChanged)_expandoObject).PropertyChanged += new PropertyChangedEventHandler(propertyChanged);

    }

    private  Control _hostControl; 
    public T BoundObject { get; private set; }
    private dynamic _expandoObject;

    private void bind(
            PropertyInfo propertyInfo,
            string controlPropertyNameToBindTo = "Text",
            string textBoxNamePrefix = default(string),
            string textBoxNameSuffix = "TextBox"
        )
    {
        try
        {
            bindExpandoField(textBoxNamePrefix + propertyInfo.Name + textBoxNameSuffix,
                                controlPropertyNameToBindTo, propertyInfo.Name);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("'{0}' error while binding '{1}' property.", ex.Message, propertyInfo.Name);
        }
    }

    private void bindExpandoField(
            string targetControlName,
            string targeControlPropertyName,
            string sourcePropertyName)
    {
        Control targetControl = _hostControl.Controls[targetControlName];
        var IDict = (IDictionary<string, object>)_expandoObject;
        var bind = new Binding(targeControlPropertyName, _expandoObject, null);

        bind.Format += (o, c) => c.Value = IDict[sourcePropertyName];
        bind.Parse += (o, c) => IDict[sourcePropertyName] = c.Value;
        targetControl.DataBindings.Add(bind);
    }

    private object Convert(object obj, Type t)
    {
        if (obj != null)
        {
            TypeConverter converter = TypeDescriptor.GetConverter(t);
            if (converter.CanConvertFrom(obj.GetType()))
            {
                return converter.ConvertFrom(obj);
            }
        }
        return null;
    }

    private void propertyChanged(object sender, PropertyChangedEventArgs e)
    {
        IDictionary<string, object> dict = sender as IDictionary<string, object>;

        foreach (PropertyInfo info in this.BoundObject.GetType().GetProperties())
        {
            if (info.Name == e.PropertyName)
            {
                object value = Convert(dict[e.PropertyName], info.PropertyType);
                info.SetValue(this.BoundObject, value);
            }
        }
    }
}

And here is an application of the above 'universal' solution to bind a WinForm to three textboxes named firstNameTextBox, lastNameTextBox and ageTextBox to an instance of a Customer custom class:

private UniversalTextBoxTextBinder<Customer> _binder;  
private void Form_Load(object sender, EventArgs e)
{
    Customer customer = new Customer()
    {
        FirstName = "Andrew",
        LastName = "Chandler",
        Age = 23
    };

    _binder = new UniversalTextBoxTextBinder<Customer>(this, customer);            
}

where Customer custom class is as simple as the following:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

Thank you.

\$\endgroup\$

migrated from stackoverflow.com Mar 13 '13 at 14:12

This question came from our site for professional and enthusiast programmers.

  • 1
    \$\begingroup\$ By the way, WPF doesn't need that, because it has a real binding engine where you can achieve a two-way binding with only doing <TextBox Text="{Binding FirstName}"/>. \$\endgroup\$ – HighCore Mar 13 '13 at 13:57
  • \$\begingroup\$ Additional tests show that the above solution doesn't provide declared in its title 'two-way' binding: the intention was to get a POCO object instance bound 'two-way' (and being 'MVVM-ready') without using INotifyPropertyChanged, but 'two-way MVVM-ready' binding in the above code works only for an ExpandoObject instance dynamically built using a source POCO object instance properties' values. 'two-way MVVM-ready' ExpandoObject does translate changed properties values to the source POCO object but no any translation of changed POCO object values is done in the opposite direction... \$\endgroup\$ – ShamilS Mar 13 '13 at 18:51
  • \$\begingroup\$ ...from POCO object instance to the corresponding ExapandoObject instance properties. So the applicability of the proposed 'universal' solution for MVVM is really questionable: it should work well in a 'disconnected' context only. I have prepared another version of code, which seems to work well in a true MVVM context but it forces to use INotifyPropertyChanged in the source 'POCO' objects, C#5.0 allows to minimize overheads on INotifyPropertyChanged implementation but anyway when INotifyPropertyChanged is implemented for a source 'POCO' objects then they can be 'two-way MVVM-ready' bound \$\endgroup\$ – ShamilS Mar 13 '13 at 18:56
  • \$\begingroup\$ ...to a WinForm (TextBox) controls without any 'universal binders', and then the only purpose of such a 'universal binder' would be dynamic run-time binding, which will minimize the need in manual coding/designing/declaration of data-binding but in the same time will force to follow some custom conventions and could impose some other limitations as any 'framework' 'universal' solutions do. \$\endgroup\$ – ShamilS Mar 13 '13 at 19:15
  • \$\begingroup\$ @HighCore: Yes, WPF (tested here in VS2012) is able to natively bind two-way ExpandoObject instances' dynamic properties. Thank you. \$\endgroup\$ – ShamilS Mar 14 '13 at 8:31
5
\$\begingroup\$

I think this code, while fairly impressive, is reinventing a wheel that WinForms wasn't designed to use. Trying to implement the Model-View-ViewModel pattern with WinForms will cause you more trouble than you can possibly imagine, if your application is just a little bit more complex than, say, a calculator app.

You're using reflection quite extensively, so your binding is inherently slow, which means your View has to be as simple as possible, because binding many controls will be more painful than it should.

WinForms applications are better off with the Model-View-Presenter pattern.

That said your naming and indentation aren't always consistent in terms of PascalCasing method names and line breaks before/after } and ).

I know @HighCore can be a little harsh at times, but he knows his stuff and he's absolutely right: if you want to simulate WPF behavior in WinForms, the real thing will be much less trouble, and leave you with much cleaner code.

Sorry if that's not what you wanted to hear...

\$\endgroup\$
  • 1
    \$\begingroup\$ MVVM in winforms is a beautiful thing. While quite complex to implement from scratch its a definite must have for any professional winforms developer. I've been using mvvm with winforms for quite a while and I just have to disagee with your opinion in this answer. Have you ever tried it yourself? \$\endgroup\$ – user28366 Oct 29 '15 at 9:09
  • 1
    \$\begingroup\$ @Meehow I tried, yes, and failed miserably. Extending it to simulate WPF-like bindings is ...bound to be a frustrating experience. And even if one manages to get a decent binding engine to work, they still have to put up with annoying layouts and the user32.dll (which originates in Windows 3.1's user.dll) rendering. I'd much rather go full-on WPF than try to turn WinForms into something it wasn't meant to be. \$\endgroup\$ – Mathieu Guindon Oct 29 '15 at 13:11
  • \$\begingroup\$ maybe I'm missing something here but textbox.DataBindings.Add() along with a ViewModel implementing INotifyPropertyChanged works just fine for bindings... \$\endgroup\$ – user28366 Oct 29 '15 at 13:55
  • 1
    \$\begingroup\$ @Meehow nah, that's fine for simple bindings. Someone blogged about how they survived WinForms databindings here, good read. \$\endgroup\$ – Mathieu Guindon Oct 29 '15 at 14:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.