how to dynamically create binding proxies?

Go To StackoverFlow.com

1

I don't know if I asked it right, but I'm kind of lost for hours, so bare with me.

My initial problem was: I have a configuration object, which has an enum and I wanted to bind a bunch of RadioButton's to the same property. As I see myself repeating it sometimes I tried to come up with a general solution for binding multiple items to the same property. The solution I found was to create a proxy which executes an expression. See the code below.

public enum GenderEnum
{
    Male,
    Female
}

public class DataClass
{
    public GenderEnum Gender { get; set; }
}

class ExpressionBinder<T>
{
    public Func<T> Getter;
    public Action<T> Setter;

    public T Value
    {
        get { return Getter.Invoke(); }
        set { Setter.Invoke(value); }
    }
    public ExpressionBinder(Func<T> getter, Action<T> setter)
    {
        Getter = getter;
        Setter = setter;
    }
}

I can use it this way:

radioMale.Tag = GenderEnum.Male;
radioFemale.Tag = GenderEnum.Female;

var proxyM = new ExpressionBinder<bool>(
    () => DataObj.Gender == (GenderEnum)radioMale.Tag,
    (val) => { if (val) DataObj.Gender = (GenderEnum)radioMale.Tag; });

var proxyF = new ExpressionBinder<bool>(
    () => DataObj.Gender == (GenderEnum)radioFemale.Tag,
    (val) => { if (val) DataObj.Gender = (GenderEnum)radioFemale.Tag; });

radioMale.DataBindings.Add("Checked", proxyM, "Value");
radioFemale.DataBindings.Add("Checked", proxyF, "Value");

It's just an example, I wanted to keep it simple. But this approach actually WORKS. By question is:

I want to encapsulate the comparison and set expressions inside a derived class.

class ComparisonBinder : ExpressionBinder {}

Unfortunately, I can only say how I would like to use it.

radioMale.DataBindings.Add("Checked", 
    new ComparisonBinder<ConfigClass>((c) c.Gender, GenderEnum.Male), 
    "Value");
radioFemale.DataBindings.Add("Checked", 
    new ComparisonBinder<ConfigClass>((c) c.Gender, GenderEnum.Female),
    "Value");

So, how would you implement this? Feel free to change my ExpressionBinder class if you need to.



The Final code

(edited again: you can't omit the TValue parameter using object type. During the conversion of the lambda expression the compiler will add a call to Convert().)

class ComparisonBinder<TSource, TValue> : ExpressionBinder<bool>
{
    private TSource instance;
    private TValue comparisonValue;
    private PropertyInfo pInfo;

    public ComparisonBinder(TSource instance, Expression<Func<TSource, TValue>> property, TValue comparisonValue)
    {
        pInfo = GetPropertyInfo(property);

        this.instance = instance;
        this.comparisonValue = comparisonValue;

        Getter = GetValue;
        Setter = SetValue;
    }

    private bool GetValue()
    {
        return comparisonValue.Equals(pInfo.GetValue(instance, null));
    }

    private void SetValue(bool value)
    {
        if (value)
        {
            pInfo.SetValue(instance, comparisonValue,null);
        }
    }

    /// <summary>
    /// Adapted from surfen's answer (http://stackoverflow.com/a/10003320/219838)
    /// </summary>
    private PropertyInfo GetPropertyInfo(Expression<Func<TSource, TValue>> propertyLambda)
    {
        Type type = typeof(TSource);

        MemberExpression member = propertyLambda.Body as MemberExpression;

        if (member == null)
            throw new ArgumentException(string.Format(
                "Expression '{0}' refers to a method, not a property.",
                propertyLambda));

        PropertyInfo propInfo = member.Member as PropertyInfo;
        if (propInfo == null)
            throw new ArgumentException(string.Format(
                "Expression '{0}' refers to a field, not a property.",
                propertyLambda));

        if (type != propInfo.ReflectedType &&
            !type.IsSubclassOf(propInfo.ReflectedType))
            throw new ArgumentException(string.Format(
                "Expresion '{0}' refers to a property that is not from type {1}.",
                propertyLambda,
                type));

        return propInfo;
    }
}

Usage:

radioMale.DataBindings.Add("Checked", 
    new ComparisonBinder<DataClass,GenderEnum>(DataObj, (x) => x.Gender, GenderEnum.Male), "Value");
radioFemale.DataBindings.Add("Checked", 
    new ComparisonBinder<DataClass,GenderEnum>(DataObj, (x) => x.Gender, GenderEnum.Female), "Value");
2012-04-03 23:15
by Saulo Vallory
Is it WinForms? or WPF - surfen 2012-04-03 23:35
It's Windows Form - Saulo Vallory 2012-04-03 23:46


2

I think this can be done using PropertyInfo and some lambda magic:

class ComparisonBinder<TSource,TValue> : ExpressionBinder<bool>
{
    public ComparisonBinder(TSource source, Expression<Func<TSource, bool>> propertyLambda, TValue comparisonValue) :base(null,null)
    {
         var propertyInfo = GetPropertyInfo<TSource,bool>(source, propertyLambda);
         this.Getter = () => comarisonValue.Equals((TValue)propertyInfo.GetValue(source));
         this.Setter = (bool value) => { if(value) propertyInfo.SetValue(source, comparisonValue); };
    }
}

Usage:

radioMale.DataBindings.Add("Checked", 
    new ComparisonBinder<ConfigClass, GenderEnum>(config, c => c.Gender, GenderEnum.Male), 
    "Value");
radioFemale.DataBindings.Add("Checked", 
    new ComparisonBinder<ConfigClass, GenderEnum>(config, c => c.Gender, GenderEnum.Female),
    "Value");

I have not tested it but hopefully you'll be able to fix any errors and use this solution.

GetPropertyInfo(): copied from https://stackoverflow.com/a/672212/724944

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda)
{
    Type type = typeof(TSource);

    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(string.Format(
            "Expresion '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}
2012-04-04 00:23
by surfen
I just came up with a code really close to this one. But thanks! I'll incorporate this GetPropertyInfo method, since I was not checking everything I should, and update the question for further reference. Now I have to make the bounding two-way. But that's another question.. - Saulo Vallory 2012-04-04 00:53