8

I am looking into strong typed Windows Forms databinding using extension methods. I have got this far following help from Xavier as below:

using System;
using System.Linq.Expressions;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public static Binding Add<T>
        (this ControlBindingsCollection dataBindings,
            object dataSource,
            Expression<Func<Control, object>> controlExpression,
            Expression<Func<T, object>> objectExpression)
    {
        return Add(dataBindings, dataSource, controlExpression, objectExpression, false);
    }

    public static Binding Add<T>
        (this ControlBindingsCollection dataBindings,
            object dataSource,
            Expression<Func<Control, object>> controlExpression,
            Expression<Func<T, object>> objectExpression,
            bool formattingEnabled)
    {
        string controlPropertyName = ProcessExpression(controlExpression.Body);
        string bindingTargetName = ProcessExpression(objectExpression.Body);

        return dataBindings
            .Add(controlPropertyName, dataSource, bindingTargetName, formattingEnabled);
    }

    public static Binding Add<T, K>
        (this ControlBindingsCollection dataBindings,
            object dataSource,
            Expression<Func<K, object>> controlExpression,
            Expression<Func<T, object>> objectExpression)
    {
        return Add(dataBindings, dataSource, controlExpression, objectExpression, false);
    }

    public static Binding Add<T, K>
        (this ControlBindingsCollection dataBindings,
            object dataSource,
            Expression<Func<K, object>> controlExpression,
            Expression<Func<T, object>> objectExpression,
            bool formattingEnabled
        )
    {
        string controlPropertyName = ProcessExpression(controlExpression.Body);
        string bindingTargetName = ProcessExpression(objectExpression.Body);

        return dataBindings.Add(controlPropertyName, dataSource, bindingTargetName, formattingEnabled);
    }

    private static string ProcessExpression(Expression expression)
    {
        string propertyName;
        if (expression is MemberExpression)
        {
            propertyName = ((MemberExpression) (expression)).Member.Name;
        }
        else if (expression is UnaryExpression)
        {
            propertyName = ((MemberExpression) ((UnaryExpression) (expression)).Operand).Member.Name;
        }
        else
        {
            throw new InvalidOperationException(
                "Unknown expression type error in DataBindingsExtensionMethods.Add<T, K>");
        }
        return propertyName;
    }
}

Now I can set up a DataBinding like this:

txtBoundInt.DataBindings.Add<Contact>
    (bindingSource, tb => tb.Text, contact => contact.Id);

Or this:

cboBoundSelectedItem.DataBindings.Add
            <Contact, ComboBox>
            (bindingSource, cbo => cbo.SelectedItem, con => con.ContactType)

There seems to be a lot of casting of expressions going on though. Is there a better way?


Edit: I did find a better way, but I got into trouble for changing this question to that answer - it's reproduced below by @Carl_G.

stuartd
  • 70,509
  • 14
  • 132
  • 163
  • Please do not modify your question into an answer. If you have found a solution, it needs to go in the answers section. To a person trying to rapidly browse google links for a solution, it is very disorienting to read "Okay, I found a solution" without even knowing what your question was, or being able to evaluate whether it is applicable to the visitor's problem. – Asad Saeeduddin Feb 05 '14 at 03:03
  • Oh well, got to play by the rules.. – stuartd Feb 05 '14 at 08:26
  • 1
    It should be noted that the new nameof() function in C# 6 could also be used to avoid using a string. https://msdn.microsoft.com/en-us/library/dn986596.aspx – Tim Friesen Jan 14 '16 at 14:50

3 Answers3

6

As the question has been edited to only include an answer, I'm including that answer here. The author probably should have left the original question alone and posted an answer to his own question. But it appears to be a very good solution.


Edit: I prefer this solution I found eventually in Google's cache (it has been deleted from the author's site) as it only needs one type specification. I don't know why the original author deleted it.

// Desired call syntax:
nameTextBox.Bind(t => t.Text, aBindingSource, (Customer c) => c.FirstName);

// Binds the Text property on nameTextBox to the FirstName property
// of the current Customer in aBindingSource, no string literals required.

// Implementation.

public static class ControlExtensions
{
    public static Binding Bind<TControl, TDataSourceItem>
        (this TControl control, 
         Expression<Func<TControl, object>> controlProperty, 
         object dataSource, 
         Expression<Func<TDataSourceItem, object>> dataSourceProperty)
         where TControl: Control
    {
        return control.DataBindings.Add
             (PropertyName.For(controlProperty), 
              dataSource, 
              PropertyName.For(dataSourceProperty));
    }
}

public static class PropertyName
{
    public static string For<T>(Expression<Func<T, object>> property)
    {
        var member = property.Body as MemberExpression;
        if (null == member)
        {
            var unary = property.Body as UnaryExpression;
            if (null != unary) member = unary.Operand as MemberExpression;
        }
        return null != member ? member.Member.Name : string.Empty;
    }
}
Community
  • 1
  • 1
Carl G
  • 17,394
  • 14
  • 91
  • 115
  • 3
    SO has a question-answer(s) format. It's designed that way so that persons with the same problem/question can search for problem/questions they have, and then benefit from or suggest solutions. I'm open to different users having different stylistic preferences, but I strongly disagree with your having deleted your original question entirely. The answer with which you replaced it now lacks all the context the question provides for it. Doing so not only going against SO's design, but it also makes it more difficult for others to benefit from the information contained herein. – Carl G Sep 26 '12 at 11:23
  • 1
    No it just put what was an answer in the correct location. So if someone decided "okay, I think I understand what this problem is about, now I'm going to scan through the answers to see if there are any good ones" they could see the good answer you had included. – Carl G Sep 26 '12 at 18:31
  • 1
    Did you see that I put your original question back at the beginning and then your answer was after it? (And now only the answer remains like you had it before.) I thought maybe you had reverted it, but maybe an admin reverted it. I don't know, I was just trying to help. – Carl G Sep 26 '12 at 18:34
  • 1
    The first thing I said was that I had copied the contents of my answer from the question that was no longer a question. I really don't see your point. – Carl G Sep 27 '12 at 15:06
  • 1
    Excuse me. In my rush to read your reply I missed that the question had been reverted (which you actually said, and I misinterpreted.) I think the solution you included must stay in some form, because it was a good one. If you want to post it under your own account, please do so and I can also delete my answer then. I just want people to benefit from the love-sandwich that is SO. Remember the horrid days when googling a programming question only returned Expert Exchange responses behind a paywall? – Carl G Sep 27 '12 at 15:15
  • 1
    This worked well for me and I like the solution. I updated the TControl type constraint to use IBindableComponent. And the 'object dataSource' parameter to use 'TDataSourceItem dataSource' which means you don't have to cast the lambda function that searches out the propery you want. – ShaunO Mar 06 '14 at 22:35
6

What about setting the return type to object?

public static Binding Add<T>
    (this ControlBindingsCollection dataBindings, object dataSource,
    Expression<Func<Control, object>> controlLambda,
    Expression<Func<T, object>> objectLambda) {
    string controlPropertyName =
          ((MemberExpression)(controlLambda.Body)).Member.Name;
    string bindingTargetName =
          ((MemberExpression)(objectLambda.Body)).Member.Name;

    return dataBindings.Add
         (controlPropertyName, dataSource, bindingTargetName);
}
Xavier Poinas
  • 19,377
  • 14
  • 63
  • 95
  • Thanks: that compiles, but yields this runtime error: Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MemberExpression'. – stuartd Aug 09 '10 at 23:49
  • Hmm. I can cast the objectLambda to a UnaryExpression, but I can't see how to get the property name from the UnaryExpression.. (the controlLambda is still a MemberExpression) – stuartd Aug 09 '10 at 23:59
  • The exception `Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MemberExpression'.` Usually occurs when you're types no longer in your function, for example when you have `Expression> ... x -> x.ID` and `x.ID` is actually of type `long`. – Seph Nov 02 '11 at 09:21
2

I have been using the code posted by Stuart for a few months now. I did add a few more overloads to match the rest of the databinding scenarios that you may want to use (I'm just posting it here for others to have an even easier time getting this very useful thing working)

    public static class ControlExtensions {

    /// <summary>Databinding with strongly typed object names</summary>
    /// <param name="control">The Control you are binding to</param>
    /// <param name="controlProperty">The property on the control you are binding to</param>
    /// <param name="dataSource">The object you are binding to</param>
    /// <param name="dataSourceProperty">The property on the object you are binding to</param>
    public static Binding Bind<TControl, TDataSourceItem>(this TControl control, Expression<Func<TControl, object>> controlProperty, object dataSource, Expression<Func<TDataSourceItem, object>> dataSourceProperty)
    where TControl :Control {
        return control.DataBindings.Add(PropertyName.For(controlProperty), dataSource, PropertyName.For(dataSourceProperty));
    }
    public static Binding Bind<TControl, TDataSourceItem>(this TControl control, Expression<Func<TControl, object>> controlProperty, object dataSource, Expression<Func<TDataSourceItem, object>> dataSourceProperty, bool formattingEnabled = false)
    where TControl :Control {
        return control.DataBindings.Add(PropertyName.For(controlProperty), dataSource, PropertyName.For(dataSourceProperty), formattingEnabled);
    }
    public static Binding Bind<TControl, TDataSourceItem>(this TControl control, Expression<Func<TControl, object>> controlProperty, object dataSource, Expression<Func<TDataSourceItem, object>> dataSourceProperty, bool formattingEnabled, DataSourceUpdateMode updateMode)
    where TControl :Control {
        return control.DataBindings.Add(PropertyName.For(controlProperty), dataSource, PropertyName.For(dataSourceProperty), formattingEnabled, updateMode);
    }
    public static Binding Bind<TControl, TDataSourceItem>(this TControl control, Expression<Func<TControl, object>> controlProperty, object dataSource, Expression<Func<TDataSourceItem, object>> dataSourceProperty, bool formattingEnabled, DataSourceUpdateMode updateMode, object nullValue)
    where TControl :Control {
        return control.DataBindings.Add(PropertyName.For(controlProperty), dataSource, PropertyName.For(dataSourceProperty), formattingEnabled, updateMode, nullValue);
    }
    public static Binding Bind<TControl, TDataSourceItem>(this TControl control, Expression<Func<TControl, object>> controlProperty, object dataSource, Expression<Func<TDataSourceItem, object>> dataSourceProperty, bool formattingEnabled, DataSourceUpdateMode updateMode, object nullValue, string formatString)
    where TControl :Control {
        return control.DataBindings.Add(PropertyName.For(controlProperty), dataSource, PropertyName.For(dataSourceProperty), formattingEnabled, updateMode, nullValue, formatString);
    }
    public static Binding Bind<TControl, TDataSourceItem>(this TControl control, Expression<Func<TControl, object>> controlProperty, object dataSource, Expression<Func<TDataSourceItem, object>> dataSourceProperty, bool formattingEnabled, DataSourceUpdateMode updateMode, object nullValue, string formatString, IFormatProvider formatInfo)
    where TControl :Control {
        return control.DataBindings.Add(PropertyName.For(controlProperty), dataSource, PropertyName.For(dataSourceProperty), formattingEnabled, updateMode, nullValue, formatString, formatInfo);
    }

    public static class PropertyName {
        public static string For<T>(Expression<Func<T, object>> property) {
            var member = property.Body as MemberExpression;
            if(null == member) {
                var unary = property.Body as UnaryExpression;
                if(null != unary) member = unary.Operand as MemberExpression;
            }
            return null != member ? member.Member.Name : string.Empty;
        }
    }

}
XenoPuTtSs
  • 1,254
  • 1
  • 11
  • 31
  • 2
    This code worked well. It needs one small fix though. Instead of constraining to a 'Control', it should be constrained to 'IBindableComponent'. That's the proper interface that has the 'DataBindings' property. – craig.tadlock Dec 15 '12 at 19:57