Category Archives: Attached Property

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!

Advertisements

¡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

    }

}