Mixing Style Setters and Bindings in WinRT

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:
    ///    &lt;Setter Property="IsSelected" Value="{Binding IsSelected}"/&gt;.
    /// </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!

Herencia en Dependency Properties

El temita jodido del otro día, cuando estuve dándole caña a las propiedades de dependencia heredables (FrameworkPropertyMetadata.Inherits)

Yo tenía una magggnífica propiedad llamada Relleno compuesta de:

  • Color (Color)
  • Id de patrón (int)
  • Tint (double)

Esta propiedad se hereda por por lo que los elementos del árbol visual. Hasta ahí, bien.

El asunto se puso marrón oscuro cuando en un hijo me daba por modificar el miembro “Color”. ¿Qué ocurría?

Resultado esperado:

image

Resultado obtenido:

image

La razón es que el Relleno es una instancia compartida entre todos los elementos de la jerarquía. El del padre es la misma instancia que el del hijo, por lo que si modificamos el Color al Relleno, efectivamente, ¡estamos cambiando la misma cosa!

¿Qué hemos de hacer? Pues la cosa quizá no es muy sencilla, pero lo primero de todo es que tenemos que tener claro qué es un Value-Object, es decir, cuándo el valor de una cosa determina qué cosa es.

Esto depende de la semántica que queramos darle. Para mí, si tengo una instancia de Relleno y le cambio el Color, serán dos patrones distintos, por lo que no sería admisible cambiar el Color, sino que debería otra crearme otra instancia de Relleno distinta (Relleno debería ser inmutable). De estas manera, 2 instancias distintas nos darían el resultado deseado.

Espero que la monserga te haya servido, pequeño saltamontes.

Hala, a echarte un Call Of Duty, que ya es escrito bastante, mamoncete.

¡Hay que guardar esto en lugar seguro! Propiedad Adjunta genérica con objeto asociado, On The Rocks!

Señoras y señores, gracias a un tipo llamado Bary Nusz (http://blog.falafel.com/blogs/BaryNusz) y sacándole código del blog he conseguid (ha conseguido) realizar una cosa flipante:

<Button x:Name=”boton”
            WpfApplication1:MouseDownTiming.Value=”{Binding ElementName=boton, Path=Focusable}”

 

¿Qué es esto? Una propiedad adjunta que se enlaza a la propiedad Focusable… bueno, realmente he desechado la solución con esta artimaña, pero aquí dejo presente lo que había en el condumio.

Principalmente:

AttachedPropertyAssociatedObject.cs

using System;

using System.Collections.Generic;

using System.Windows;

 

namespace WpfApplication1

{

    public abstract class AttachedPropertyAssociatedObject<O, T, A> : DependencyObject

        where T : DependencyObject

        where O : AttachedPropertyAssociatedObject<O, T, A>, new()

    {

        static Type _type = typeof(O);

 

        public T AssociatedObject { get; private set; }

 

        public AttachedPropertyAssociatedObject()

        { }

 

        public AttachedPropertyAssociatedObject(T associatedObject)

        {

            AssociatedObject = associatedObject;

        }

 

        public static readonly DependencyProperty ValueProperty =

                            DependencyProperty.RegisterAttached(

        "Value",

        typeof(A),

        typeof(AttachedPropertyAssociatedObject<O, T, A>),

#if Silverlight

 new FrameworkPropertyMetadata(false, OnValueChanged));

#else

 new PropertyMetadata(OnValueChanged));

#endif

 

        public static A GetValue(DependencyObject d)

        {

            return (A)d.GetValue(ValueProperty);

        }

 

        public static void SetValue(DependencyObject d, A value)

        {

            d.SetValue(ValueProperty, value);

        }

 

        public static void AddValueChangedHandler(DependencyObject sender, PropertyChangedCallback callback)

        {

            Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =

                GenericAttachedPropertyChangedCallbackDictionary.GetValue(sender);

            if (propertyChangedCallbackDictionary == null)

            {

                propertyChangedCallbackDictionary = new Dictionary<Type, PropertyChangedCallback>();

                sender.SetValue(GenericAttachedPropertyChangedCallbackDictionary.ValueProperty, propertyChangedCallbackDictionary);

            }

            if (propertyChangedCallbackDictionary.ContainsKey(_type))

            {

                propertyChangedCallbackDictionary[_type] += callback;

            }

            else

            {

                propertyChangedCallbackDictionary[_type] = callback;

            }

        }

 

        public static void RemoveValueChangedHandler(DependencyObject sender, PropertyChangedCallback callback)

        {

            Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =

                GenericAttachedPropertyChangedCallbackDictionary.GetValue(sender);

 

            if ((propertyChangedCallbackDictionary != null) &&

                           (propertyChangedCallbackDictionary.ContainsKey(_type)))

            {

                propertyChangedCallbackDictionary[_type] -= callback;

            }

        }

 

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {

            T associatedObject = d as T;

            if (associatedObject == null)

            {

                throw new Exception(String.Format("DependencyObject must be of type {0}", typeof(T)));

            }

 

            O attachedPropertyAssociatedObject = GenericAttachedProperty<O>.GetValue(d);

            if (e.NewValue != null)

            {

                if (attachedPropertyAssociatedObject == null)

                {

                    attachedPropertyAssociatedObject = new O();

                    attachedPropertyAssociatedObject.AssociatedObject = associatedObject;

 

                    GenericAttachedProperty<O>.SetValue(associatedObject, attachedPropertyAssociatedObject);

                    attachedPropertyAssociatedObject.Initialize();

                }

                else

                {

                    Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =

                        GenericAttachedPropertyChangedCallbackDictionary.GetValue(associatedObject);

 

                    if ((propertyChangedCallbackDictionary != null) &&

                                           (propertyChangedCallbackDictionary.ContainsKey(_type)))

                    {

                        PropertyChangedCallback callback = propertyChangedCallbackDictionary[_type];

                        if (callback != null)

                        {

                            callback(associatedObject, e);

                        }

                    }

                }

            }

            else

            {

                if (attachedPropertyAssociatedObject != null)

                {

                    attachedPropertyAssociatedObject.UnInitialize();

                    GenericAttachedProperty<O>.SetValue(attachedPropertyAssociatedObject, null);

                }

            }

        }

 

        public virtual void Initialize()

        {

        }

 

        public virtual void UnInitialize()

        {

        }

    }

 

 

    public class GenericAttachedProperty<T>

    {

        public static readonly DependencyProperty ValueProperty =

                       DependencyProperty.RegisterAttached(

        "Value",

        typeof(T),

        typeof(GenericAttachedProperty<T>),

        null);

 

        public static T GetValue(DependencyObject d)

        {

            return (T)d.GetValue(ValueProperty);

        }

 

        public static void SetValue(DependencyObject d, T value)

        {

            d.SetValue(ValueProperty, value);

        }

    }

}

GenericAttachedProperty.cs

using System.Windows;

 

namespace WpfApplication1

{

    public abstract class GenericAttachedProperty<O, A>

    {

        public static readonly DependencyProperty ValueProperty =

            DependencyProperty.RegisterAttached(

                "Value",

                typeof(A),

                typeof(GenericAttachedProperty<O, A>),

                null);

 

        public static A GetValue(DependencyObject d)

        {

            return (A)d.GetValue(ValueProperty);

        }

 

        public static void SetValue(DependencyObject d, A value)

        {

            d.SetValue(ValueProperty, value);

        }

    }

}

GenericAttachedPropertyChangedCallbackDictionary.cs

namespace WpfApplication1

{

 class 

GenericAttachedPropertyChangedCallbackDictionary 

:

 

GenericAttachedProperty<GenericAttachedPropertyChangedCallbackDictionary, 

Dictionary<Type, PropertyChangedCallback>> { }

}

Y mi clasecilla personalizada:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Input;

using System.Windows.Threading;

 

namespace WpfApplication1

{

    class MouseDownTiming : AttachedPropertyAssociatedObject<MouseDownTiming, FrameworkElement, bool>

    {

 

        public override void Initialize()

        {

            AssociatedObject.PreviewMouseDown += AssociatedObjectOnPreviewMouseDown;

            AssociatedObject.PreviewMouseUp += AssociatedObjectOnPreviewMouseUp;

            AssociatedObject.SetValue(ValueProperty, false);

            SetTimer(AssociatedObject, new DispatcherTimer());

        }

 

        private void AssociatedObjectOnPreviewMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)

        {

            var timer = (DispatcherTimer)AssociatedObject.GetValue(TimerProperty);

            timer.IsEnabled = false;

            AssociatedObject.SetValue(ValueProperty, false);

        }

 

        public override void UnInitialize()

        {

            AssociatedObject.PreviewMouseDown -= AssociatedObjectOnPreviewMouseDown;

        }

 

        private void AssociatedObjectOnPreviewMouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)

        {

            var timer = (DispatcherTimer)AssociatedObject.GetValue(TimerProperty);

            timer.IsEnabled = true;

        }

 

        #region Timeout

 

        /// <summary>

        /// Timeout Attached Dependency Property

        /// </summary>

        public static readonly DependencyProperty TimeoutProperty =

            DependencyProperty.RegisterAttached("Timeout", typeof(TimeSpan), typeof(MouseDownTiming),

                new FrameworkPropertyMetadata(TimeSpan.FromSeconds(1),

                    OnTimeoutChanged));

 

        /// <summary>

        /// Gets the Timeout property. This dependency property 

        /// indicates ....

        /// </summary>

        public static TimeSpan GetTimeout(DependencyObject d)

        {

            return (TimeSpan)d.GetValue(TimeoutProperty);

        }

 

        /// <summary>

        /// Sets the Timeout property. This dependency property 

        /// indicates ....

        /// </summary>

        public static void SetTimeout(DependencyObject d, TimeSpan value)

        {

            d.SetValue(TimeoutProperty, value);

        }

 

        /// <summary>

        /// Handles changes to the Timeout property.

        /// </summary>

        private static void OnTimeoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {

            TimeSpan oldTimeout = (TimeSpan)e.OldValue;

            TimeSpan newTimeout = (TimeSpan)d.GetValue(TimeoutProperty);

 

            var timer = (DispatcherTimer) d.GetValue(TimerProperty);

            if (timer == null)

            {

                timer=(DispatcherTimer) CoerceTimer(d, e);

                SetTimer(d, timer);

            }           

 

            timer.Interval = newTimeout;

        }

 

        #endregion        

 

        #region Timer

 

        /// <summary>

        /// Timer Read-Only Dependency Property

        /// </summary>

        private static readonly DependencyPropertyKey TimerPropertyKey

            = DependencyProperty.RegisterAttachedReadOnly("Timer", typeof(DispatcherTimer), typeof(MouseDownTiming),

                new FrameworkPropertyMetadata(null,

                    new PropertyChangedCallback(OnTimerChanged),

                    new CoerceValueCallback(CoerceTimer)));

 

        public static readonly DependencyProperty TimerProperty

            = TimerPropertyKey.DependencyProperty;

 

        /// <summary>

        /// Gets the Timer property. This dependency property 

        /// indicates ....

        /// </summary>

        public static DispatcherTimer GetTimer(DependencyObject d)

        {

            return (DispatcherTimer)d.GetValue(TimerProperty);

        }

 

        /// <summary>

        /// Provides a secure method for setting the Timer property.  

        /// This dependency property indicates ....

        /// </summary>

        private static void SetTimer(DependencyObject d, DispatcherTimer value)

        {

            d.SetValue(TimerPropertyKey, value);

        }

 

        /// <summary>

        /// Handles changes to the Timer property.

        /// </summary>

        private static void OnTimerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {

            DispatcherTimer oldTimer = (DispatcherTimer)e.OldValue;

            DispatcherTimer newTimer = (DispatcherTimer)d.GetValue(TimerProperty);

 

            newTimer.Interval = (TimeSpan)d.GetValue(TimeoutProperty);

 

            newTimer.Tick += (sender, args) =>

            {

                d.SetValue(ValueProperty, true);

                var timer = (DispatcherTimer) d.GetValue(TimerProperty);

                

                timer.IsEnabled = false;

            };

        }

 

        /// <summary>

        /// Coerces the Timer value.

        /// </summary>

        private static object CoerceTimer(DependencyObject d, object value)

        {

            DispatcherTimer timer=new DispatcherTimer();

            timer.Interval = (TimeSpan) d.GetValue(TimeoutProperty);

            return timer;

        }

 

        #endregion

    }

}