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");
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;
}
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