Tag Archives: XAML

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

Introducing VisualDesigner, my library for developing visual designers in .NET

Hi friends! I’m developing a personal project just for fun. Its goal is to serve as a starting point to make any visual designer with the best user interaction possible. Drag operations, resize, grouping, snapping, copy&paste…

The code is really abstract, decoupled and following the best software engineering principles I know to make it pure Clean Code. In fact, I have made a big effort to include almost all the code in a Portable Class Library (PCL).

For the moment, it supports basic drag (move), resize, multi-selection and SNAPPING!

I hope you go there and try it. I think it’s really interesting. The main application is WPF demo application, but the main functionality is almost totally platform-agnostic.

The project site in GitHub is this: https://github.com/SuperJMN/VisualDesigner

Feel free to collaborate or suggest anything. Maybe we can learn a lot sharing thoughts or even code 🙂

Thanks.

ComboBox to link one entity to another.

I don’t forget WPF! Here is a pretty common situation in which you may need to link (via UI) two entities.

For example, you have a list of people and a list of programming languages.

In a view need to assign to each person their favorite programming language.

  • José Manuel => C#
  • Luis Javier => Java

Something like this:

Linking

This is the code I had to write! 🙂 (The XAML is the important thing here).

<Window x:Class="Lenguage.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lenguage="clr-namespace:Lenguage"
        xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <collections:ArrayList x:Key="People">
            <lenguage:Person Name="José Manuel"></lenguage:Person>
            <lenguage:Person Name="Luis Javier"></lenguage:Person>
        </collections:ArrayList>
        <collections:ArrayList x:Key="ProgrammingLanguages">
            <lenguage:ProgrammingLanguage Name="C#"></lenguage:ProgrammingLanguage>
            <lenguage:ProgrammingLanguage Name="Java"></lenguage:ProgrammingLanguage>
        </collections:ArrayList>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{StaticResource People}" AutoGenerateColumns="False" CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Person" Width="*" Binding="{Binding Name}" />
                <DataGridTemplateColumn Header="Favorite language" Width="*">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate DataType="{x:Type lenguage:Person}">
                            <ComboBox DisplayMemberPath="Name"                                
                                      ItemsSource="{StaticResource ProgrammingLanguages}"
                                      SelectedItem="{Binding FavoriteLanguage, UpdateSourceTrigger=PropertyChanged}"
                                      IsSynchronizedWithCurrentItem="False"                                      
                                      />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Using GridSplitter? Better to see it working :)

Well, this is a practical usage of it:

http://zamjad.wordpress.com/2009/12/25/using-gridsplitter/

Be sure to check it! it’s clarifying 🙂

But, baby, you can see the actual XAML code filtered for your convenience!

   <Grid>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="Auto" />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <GridSplitter Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3" Background="LightGray"
                          Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            <GridSplitter Grid.Column="1" Grid.Row="0" Grid.RowSpan="3" Background="LightGray"
                          Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            <Rectangle Grid.Column="0" Grid.Row="0" Margin="5" Fill="Red" />
            <Rectangle Grid.Column="2" Grid.Row="0" Margin="5" Fill="Blue" />
            <Rectangle Grid.Column="0" Grid.Row="2" Margin="5" Fill="Green" />
            <Rectangle Grid.Column="2" Grid.Row="2" Margin="5" Fill="Yellow" />
        </Grid>