Showing posts with label binding. Show all posts
Showing posts with label binding. Show all posts

Sunday, October 27, 2013

Bind WPF controls to attributes using Caliburn Micro

To bind for example an DecimalUpDown control to a RangeAttribute specified in a domain model, or a maxlength to a StringLengthAttribute you need to change the automatic binding of Caliburn Micro.

The example below is for a DecimalUpDown, but you can use it for all kind of fun stuff. In my github working example you can also see a Textbox.

First we need to add an ElementConvention for out DecimalUpDown in the BootStrapper's Configure method. Override this method and add the following convention (edit 2014-03-26 added attribute check to prevent binding errors):
ConventionManager.AddElementConvention<DecimalUpDown>(DecimalUpDown.ValueProperty, "Value", "ValueChanged").ApplyBinding =
(viewModelType, path, property, element, convention) =>
{
    if (!ConventionManager.SetBindingWithoutBindingOrValueOverwrite(viewModelType, path, property, element, convention, DecimalUpDown.ValueProperty))
        return false;

    if (property.GetCustomAttributes(typeof (RangeAttribute), true).Any())
    {
        if (!ConventionManager.HasBinding(element, DecimalUpDown.MaximumProperty))
        {
            var binding = new Binding(path) {Mode = BindingMode.OneTime, Converter = RangeMaximumConverter, ConverterParameter = property};
            BindingOperations.SetBinding(element, DecimalUpDown.MaximumProperty, binding);
        }

        if (!ConventionManager.HasBinding(element, DecimalUpDown.MinimumProperty))
        {
            var binding = new Binding(path) {Mode = BindingMode.OneTime, Converter = RangeMinimumConverter, ConverterParameter = property};
            BindingOperations.SetBinding(element, DecimalUpDown.MinimumProperty, binding);
        }
    }

    return true;
};
As you can see the binding uses a RangeMaximumConverter and a RangeMinimumConverter. These are fairly simple with the AttributeConverter baseclass:
public sealed class RangeMaximumConverter : AttributeConverter<RangeAttribute>
{
    public override object GetValueFromAttribute(RangeAttribute attribute)
    {
        return attribute.Maximum;
    }
}
And the AttributeConverter base class:
public abstract class AttributeConverter<T> : IValueConverter
    where T : Attribute
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var property = parameter as PropertyInfo;

        if (property == null)
            return new ArgumentNullException("parameter").ToString();

        if (!property.IsDefined(typeof(T), true))
            return new ArgumentOutOfRangeException("parameter", parameter,
                "Property \"" + property.Name + "\" has no associated " + typeof(T).Name + " attribute.").ToString();

        return GetValueFromAttribute((T)property.GetCustomAttributes(typeof(T), true)[0]);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }

    public abstract object GetValueFromAttribute(T attribute);
}
You can find a working example in GitHub.

Happy coding,
Luuk