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