views:

260

answers:

4

This probably applies to other places, but in WinForms, when I use binding I find many methods want to take the name of the property to bind to. Something like:

class Person
{
    public String Name { get { ... } set { ... } }
    public int Age { get { ... } set { ... } }
}

class PersonView
{
    void Bind(Person p)
    {
        nameControl.Bind(p,"Name");
        ageControl.Bind(p,"Age");
    }
}

The big problem I keep having with this is that "Name" and "Age" are specified as strings. This means the compiler is no help if someone renames one of Person's properties. The code will compile fine, but the bindings will be broken.

Is there a standard way of solving this that I've missed? It feels like I need some keyword, maybe called stringof to match the existing typeof. You could use it something like:

ageControl.Bind(p,stringof(p.Age).Name);

stringof could return some class that has properties for getting the full path, part of the path, or the string so you can parse it up yourself.

Is something like this already do-able?

A: 

You could use reflection to find the name ;-)

This of course would be a circular reference, you'd use the name that you think it is to find the same name (or to not find anything, meaning the property was renamed... But there's an idea (or rather, a trick) : by making a do-nothing reference to the property you wish to use, you'd get compile time confirmation that it is still there. Only problem is if someone merely swap various property names around; in that case, the names still exist (no compile-time error), but have different application-level semantics (possible surprises in the application's output)

mjv
+3  A: 

Hi!

Have a look at this code snippet I've posted in another question, it can help you! (But only, if you are using .NET 3.5)

Best Regards
Oliver Hanappi

Oliver Hanappi
+3  A: 

You can do that with expression trees, as explained in this question

protected static string GetPropertyName<TSource, TResult>(Expression<Func<TSource, TResult>> expression)
{
    if (expression.NodeType == ExpressionType.Lambda && expression.Body.NodeType == ExpressionType.MemberAccess)
    {
        PropertyInfo prop = (expression.Body as MemberExpression).Member as PropertyInfo;
        if (prop != null)
        {
            return prop.Name;
        }
    }
    throw new ArgumentException("expression", "Not a property expression");
}

...

ageControl.Bind(p, GetPropertyName((Person p) => p.Age));
Thomas Levesque
+1  A: 

You can use Expressions to get compiler-checked bindings. For example, in one of current projects we set up bindings like this:

DataBinder
    .BindToObject(this)
    .ObjectProperty(c => c.IsReadOnly)
        .Control(nameTextBox, n => n.ReadOnly)
        .Control(addressControl, n => n.ReadOnly)

Code supporting this style is separated into several classes:

public static class DataBinder
{
    public static DataBinderBindingSourceContext<TDataSource> BindToObject<TDataSource>(TDataSource dataSource)
    {
        return new DataBinderBindingSourceContext<TDataSource>(dataSource);
    }
}

public class DataBinderBindingSourceContext<TDataSource> 
{
    public readonly object DataSource;

    public DataBinderBindingSourceContext(object dataSource)
    {
        DataSource = dataSource;
    }

    public DataBinderControlContext<TDataSource, TProperty> ObjectProperty<TProperty>(Expression<Func<TDataSource, TProperty>> property)
    {
        return new DataBinderControlContext<TDataSource, TProperty>(this, property);
    }
}

public class DataBinderControlContext<TDataSource, TProperty>
{
    readonly DataBinderBindingSourceContext<TDataSource> BindingSourceContext;
    readonly string ObjectProperty;

    public DataBinderControlContext
        (
            DataBinderBindingSourceContext<TDataSource> bindingSourceContext,
            Expression<Func<TDataSource, TProperty>> objectProperty
        )
    {
        BindingSourceContext = RequireArg.NotNull(bindingSourceContext);
        ObjectProperty = ExpressionHelper.GetPropertyName(objectProperty);
    }

    public DataBinderControlContext<TDataSource, TProperty> Control<TControl>(TControl control, Expression<Func<TControl, TProperty>> property)
        where TControl : Control
    {
        var controlPropertyName = ExpressionHelper.GetPropertyName(property);
        control.DataBindings.Add(controlPropertyName, BindingSourceContext.DataSource, ObjectProperty, true);

        return this;
    }
}

public static class ExpressionHelper
{
    public static string GetPropertyName<TResult>(Expression<Func<TResult>> property)
    {
        return GetMemberNames(((LambdaExpression)property).Body).Skip(1).Join(".");
    }

    public static string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> property)
    {
        return GetMemberNames(((LambdaExpression)property).Body).Join(".");
    }

    static IEnumerable<string> GetMemberNames(Expression expression)
    {
        if (expression is ConstantExpression || expression is ParameterExpression)
            yield break;

        var memberExpression = (MemberExpression)expression;

        foreach (var memberName in GetMemberNames(memberExpression.Expression))
            yield return memberName;

        yield return memberExpression.Member.Name;
    }
}

public static class StringExtentions
{
    public static string Join(this IEnumerable<string> values, string separator)
    {
        if (values == null)
            return null;

        return string.Join(separator, values.ToArray());
    }
}
Konstantin Spirin