Thursday, March 15, 2012

Fixing CornerRadius in Silverlight

OK - I know I've not posted in nearly a year... but this time I'm covering one of those "why the frack does it work like that" issues with Silverlight 4.

I hit this one today - I wanted to unify the radii of corners within a Silverlight application to replace the random set of radii used (1, 2, 4, 5, 8, 10, 12, 15 and 20 pixels no less) with a simpler set - small, medium, large and massive.

The problem is that (for whatever reason), the developers of Silverlight decided that we didn't need to be able to create CornerRadius objects in our ResourceDictionaries. Or rather that we could TRY, but that the XAML Loader was never told how to deal with them. Even though the design surfaces in Visual Studio and Expression Blend can quite happily deal with this!

So if you *DO* try then you either get an error saying that you can't set the readonly TopLeft property of a CornerRadius, or if, like me, you do this in your theme.xaml you just get a catastrophic error - somewhere in your project.

So what was the solution?

Well, there was a very good answer to exactly this on Stack Overflow - using a Value Converter to read named resource values from the object for which we want to set the CornerRadius... But I suddenly realised that there was no need to have a "magic string" as keys for the TopLeft etc values - we can just use properties on the value converter ITSELF!

The result is a pretty elegant way to get around a limitation of Silverlight, thus:


<Border x:Name="buttonBorder">

<Border.CornerRadius>

<Binding ElementName="buttonBorder">

<Binding.Converter>

<Converters:DynamicCornerRadiusConverter TopLeft="{StaticResource massiveCornerRadiusValue}"

TopRight="{StaticResource massiveCornerRadiusValue}"

BottomRight="{StaticResource massiveCornerRadiusValue}"

BottomLeft="0.0" />

Binding.Converter>

Binding>

Border.CornerRadius>

Border>


Not quite as simple as defining a CornerRadius in a ResourceDictionary, but not bad at all - and certainly it allowed me to do what I wanted - to rationalise my myriad of Radii!

And here's the code:

namespace CheviotConsulting.Common.Silverlight.Converters
{
    using System.Windows;
    using System.Windows.Data;
 
    /// 
    /// Exposes a CornerRadius instance configured for a ValuationProgressButton
    /// 
    public class DynamicCornerRadiusConverter : IValueConverter
    {
        /// 
        /// Gets or sets the top left.
        /// 
        /// The top left.
        public double TopLeft { get; set; }
 
        /// 
        /// Gets or sets the top right.
        /// 
        /// The top right.
        public double TopRight { get; set; }
 
        /// 
        /// Gets or sets the bottom left.
        /// 
        /// The bottom left.
        public double BottomLeft { get; set; }
 
        /// 
        /// Gets or sets the bottom right.
        /// 
        /// The bottom right.
        public double BottomRight { get; set; }
 
        /// 
        /// Gets the corner radius.
        /// 
        /// The resource source.
        /// the corner radius
        private CornerRadius GetCornerRadius(FrameworkElement resourceSource)
        {
            var result = new CornerRadius(this.TopLeft, this.TopRight, this.BottomRight, this.BottomLeft);
            return result;
        }
 
        /// 
        /// Modifies the source data before passing it to the target for display in the UI.
        /// 
        /// The source data being passed to the target.
        /// The  of data expected by the target dependency property.
        /// An optional parameter to be used in the converter logic.
        /// The culture of the conversion.
        /// 
        /// The value to be passed to the target dependency property.
        /// 
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return this.GetCornerRadius(value as FrameworkElement);
        }
 
        /// 
        /// Modifies the target data before passing it to the source object.  This method is called only in cref="F:System.Windows.Data.BindingMode.TwoWay"/> bindings.
        /// 
        /// The target data being passed to the source.
        /// The  of data expected by the source object.
        /// An optional parameter to be used in the converter logic.
        /// The culture of the conversion.
        /// 
        /// The value to be passed to the source object.
        /// 
        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new System.NotImplementedException();
        }
    }
}