To say the truth, this caught me by surprise. When I stumbled upon the fact that in WinRT you cannot just assign a Binding to a Property Setter. Surprisingly enough, this is not supported.
So no <Setter Property=»Blah» Value=»{Binding Path=Bleh}» />
But some smart dudes have coined a method to make it work. At least, the did in Silverlight 4 (the version 5 already supports Bindings in those Setters). Later, the trick came to WinRT… they say WinRT was forked from Silverlight 4. Now a lot of things make sense :S
What’s the trick? Using Attached Properties.
It uses a helper class to wire up everything. It may seem ugly at first sight, but while WinRT is so restrictive (and much more for a WPF developer like me), it’s the only way I can think of that is more of less XAML oriented.
This is the helper class (WinRT, of course):
// Copyright (C) Microsoft Corporation. All Rights Reserved. // This code released under the terms of the Microsoft Public License // (Ms-PL, http://opensource.org/licenses/ms-pl.html). using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Markup; namespace Delay { /// <summary> /// Class that implements a workaround for a Silverlight XAML parser /// limitation that prevents the following syntax from working: /// <Setter Property="IsSelected" Value="{Binding IsSelected}"/>. /// </summary> [ContentProperty(Name = "Values")] public class SetterValueBindingHelper { /// <summary> /// Gets or sets an optional type parameter used to specify the type /// of an attached DependencyProperty as an assembly-qualified name, /// full name, or short name. /// </summary> [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Unambiguous in XAML.")] public string Type { get; set; } /// <summary> /// Gets or sets a property name for the normal/attached /// DependencyProperty on which to set the Binding. /// </summary> public string Property { get; set; } /// <summary> /// Gets or sets a Binding to set on the specified property. /// </summary> public Binding Binding { get; set; } /// <summary> /// Gets a Collection of SetterValueBindingHelper instances to apply /// to the target element. /// </summary> /// <remarks> /// Used when multiple Bindings need to be applied to the same element. /// </remarks> public Collection<SetterValueBindingHelper> Values { get { // Defer creating collection until needed if (null == _values) { _values = new Collection<SetterValueBindingHelper>(); } return _values; } } /// <summary> /// Backing store for the Values property. /// </summary> private Collection<SetterValueBindingHelper> _values; /// <summary> /// Gets the value of the PropertyBinding attached DependencyProperty. /// </summary> /// <param name="element">Element for which to get the property.</param> /// <returns>Value of PropertyBinding attached DependencyProperty.</returns> [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "SetBinding is only available on FrameworkElement.")] public static SetterValueBindingHelper GetPropertyBinding(FrameworkElement element) { if (null == element) { throw new ArgumentNullException("element"); } return (SetterValueBindingHelper)element.GetValue(PropertyBindingProperty); } /// <summary> /// Sets the value of the PropertyBinding attached DependencyProperty. /// </summary> /// <param name="element">Element on which to set the property.</param> /// <param name="value">Value forPropertyBinding attached DependencyProperty.</param> [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "SetBinding is only available on FrameworkElement.")] public static void SetPropertyBinding(FrameworkElement element, SetterValueBindingHelper value) { if (null == element) { throw new ArgumentNullException("element"); } element.SetValue(PropertyBindingProperty, value); } /// <summary> /// PropertyBinding attached DependencyProperty. /// </summary> public static readonly DependencyProperty PropertyBindingProperty = DependencyProperty.RegisterAttached( "PropertyBinding", typeof(SetterValueBindingHelper), typeof(SetterValueBindingHelper), new PropertyMetadata(null, OnPropertyBindingPropertyChanged)); /// <summary> /// Change handler for the PropertyBinding attached DependencyProperty. /// </summary> /// <param name="d">Object on which the property was changed.</param> /// <param name="e">Property change arguments.</param> private static void OnPropertyBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // Get/validate parameters var element = (FrameworkElement)d; var item = (SetterValueBindingHelper)e.NewValue; if (null != item) { // Item value present if ((null == item.Values) || (0 == item.Values.Count)) { // No children; apply the relevant binding ApplyBinding(element, item); } else { // Apply the bindings of each child foreach (var child in item.Values) { if ((null != item.Property) || (null != item.Binding)) { throw new ArgumentException( "A SetterValueBindingHelper with Values may not have its Property or Binding set."); } if (0 != child.Values.Count) { throw new ArgumentException( "Values of a SetterValueBindingHelper may not have Values themselves."); } ApplyBinding(element, child); } } } } /// <summary> /// Applies the Binding represented by the SetterValueBindingHelper. /// </summary> /// <param name="element">Element to apply the Binding to.</param> /// <param name="item">SetterValueBindingHelper representing the Binding.</param> private static void ApplyBinding(FrameworkElement element, SetterValueBindingHelper item) { if ((null == item.Property) || (null == item.Binding)) { throw new ArgumentException( "SetterValueBindingHelper's Property and Binding must both be set to non-null values."); } // Get the type on which to set the Binding TypeInfo type = null; if (null == item.Type) { // No type specified; setting for the specified element type = element.GetType().GetTypeInfo(); } else { // Try to get the type from the type system type = System.Type.GetType(item.Type).GetTypeInfo(); if (null == type) { // Search for the type in the list of assemblies foreach (var assembly in AssembliesToSearch) { // Match on short or full name type = assembly.DefinedTypes .Where(t => (t.FullName == item.Type) || (t.Name == item.Type)) .FirstOrDefault(); if (null != type) { // Found; done searching break; } } if (null == type) { // Unable to find the requested type anywhere throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, "Unable to access type \"{0}\". Try using an assembly qualified type name.", item.Type)); } } } // Get the DependencyProperty for which to set the Binding DependencyProperty property = null; var allProperties = type.GetAllProperties(); var field = allProperties.FirstOrDefault(info => info.Name.Equals(item.Property + "Property")); if (null != field) { property = field.GetValue(null) as DependencyProperty; } if (null == property) { // Unable to find the requsted property throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, "Unable to access DependencyProperty \"{0}\" on type \"{1}\".", item.Property, type.Name)); } // Set the specified Binding on the specified property element.SetBinding(property, item.Binding); } /// <summary> /// Gets a sequence of assemblies to search for the provided type name. /// </summary> private static IEnumerable<Assembly> AssembliesToSearch { get { // Start with the System.Windows assembly (home of all core controls) yield return typeof(Control).GetTypeInfo().Assembly; #if SILVERLIGHT && !WINDOWS_PHONE // Fall back by trying each of the assemblies in the Deployment's Parts list foreach (var part in Deployment.Current.Parts) { var streamResourceInfo = Application.GetResourceStream( new Uri(part.Source, UriKind.Relative)); using (var stream = streamResourceInfo.Stream) { yield return part.Load(stream); } } #endif } } } public static class ReflectionExtensions { public static IEnumerable<PropertyInfo> GetAllProperties(this TypeInfo type) { var list = type.DeclaredProperties.ToList(); var subtype = type.BaseType; if (subtype != null) list.AddRange(subtype.GetTypeInfo().GetAllProperties()); return list.ToArray(); } } }
And the other important thing is to know how to use it inside XAML:
<Button Grid.Column="1" Grid.ColumnSpan="2" DataContext="Coco"> <Button.Style> <Style TargetType="Button"> <!-- Equivalent WPF syntax: <Setter Property="Content" Value="{Binding}"/> --> <Setter Property="delay:SetterValueBindingHelper.PropertyBinding"> <Setter.Value> <delay:SetterValueBindingHelper Property="Content" Binding="{Binding}"/> </Setter.Value> </Setter> </Style> </Button.Style> </Button>
Another example from my Project VisualDesigner:
<designSurface:DesignSurface Background="PowderBlue" ItemTemplateSelector="{StaticResource TypedTemplateSelector}" ItemsSource="{Binding Items}" Grid.Row="1"> <!--<designSurface:DesignSurface.ItemsPanel> <ItemsPanelTemplate> <Canvas /> </ItemsPanelTemplate> </designSurface:DesignSurface.ItemsPanel>--> <designSurface:DesignSurface.ItemContainerStyle> <Style TargetType="winRt:CanvasItemControl"> <Setter Property="delay:SetterValueBindingHelper.PropertyBinding"> <Setter.Value> <delay:SetterValueBindingHelper> <delay:SetterValueBindingHelper Type="Canvas" Property="Left" Binding="{Binding Left, Mode=TwoWay}" /> <delay:SetterValueBindingHelper Type="Canvas" Property="Top" Binding="{Binding Top, Mode=TwoWay}" /> <delay:SetterValueBindingHelper Property="Width" Binding="{Binding Width, Mode=TwoWay}" /> <delay:SetterValueBindingHelper Property="Height" Binding="{Binding Height, Mode=TwoWay}" /> </delay:SetterValueBindingHelper> </Setter.Value> </Setter> </Style> </designSurface:DesignSurface.ItemContainerStyle> </designSurface:DesignSurface>
Finally, I would like to give thanks to Mark Smith (@marksm in Twitter), for pointing me out some interesting posts to the solution. He has supported me from the beginning. These were the links that he gave me 🙂
Thanks to them, too!