Using Dynamic XAML in Windows 8 Metro

This article describes how the Windows 8 Metro Consumer Preview deals with three standard ways of dynamically applying a look-and-feel to XAML controls. When working in Metro with XAML and data, you will want to maximally leverage the data binding capabilities of the platform from day one. You can do this at multiple levels. In this article, I will discuss the following techniques: value converters, style selectors, and data template selectors.

 Here's a screenshot of the attached sample project:

Using a Value Converter

Dependency properties of a XAML control can be directly bound to properties of the viewModel. Sometimes a conversion of value and/or data type needs to take place; that can be done through a ValueConverter. A value converter is a class that implements the IValueConverter interface, with just Convert and ConvertBack functions. Here's an example of a string-to-string converter that translates official beer colors into HTML named colors:

using System;
using Windows.UI.Xaml.Data;

public class BeerColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {

        switch (value.ToString())
        {
            case "Yellow":
                return "LemonChiffon";
            case "Straw":
                return "Wheat";
            case "Gold":
                return "GoldenRod";
            case "Amber":
            case "LightCopper":
            case "Copper":
                return "Peru";
            case "LightBrown":
                return "Chocolate";
            case "Brown":
                return "Brown";
            case "DarkBrown":
                return "darkRed";
            case "VeryDarkBrown":
                return "SaddleBrown";
            case "Black":
                return "Black";
            default:
                return "Lime";
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return value;
    }
}

Just define an instance of the converter as a resource:

<Page.Resources>
    <local:BeerColorConverter x:Key="BeerColorConverter" />
</Page.Resources>

And use it in the binding:

<TextBlock Text="{Binding Name}" Foreground="{Binding Color, Converter={StaticResource BeerColorConverter}}" />

Unfortunately Metro doesn't ship with MultiBinding, and bindings don't come with a StringFormat option. That definitely restricts the usage of a value converter, compared to WPF and Silverlight.

Using a Style Selector

Another way of dynamically styling items in a XAML list control, is with a StyleSelector. This provides a way to apply styles based on custom logic. This seems to be the only way to create a dynamic style in Metro, due to the absence of DataTriggers. Just create a subclass of the StyleSelector class and implement the SelectStyle method. The bound item is the first parameter, the item's style is the return value. Here's a small example on how to modify the style for a list view item:

public class BeerStyleSelector: StyleSelector
{
    protected override Style SelectStyleCore(object item, DependencyObject container)
    {
        Style style = new Style(typeof(ListViewItem));
        style.Setters.Add(new Setter(ListViewItem.ForegroundProperty, new SolidColorBrush(Colors.Red)));
        return style;
    }
}

Again, the class containing the logic should be defined as a resource:

<Page.Resources>
    <local:BeerStyleSelector x:Key="BeerStyleSelector" />
</Page.Resources>

 You can then assign the ItemContainerStyleSelector to an instance of it:

<ListView 
     ItemsSource="{Binding BelgianBeers}"
     ItemContainerStyleSelector="{StaticResource BeerStyleSelector}" />

I did not elaborate this example to much, since unfortunately style selectors don't work in the Consumer Preview. Hopefully the problem will be solved next week, with the Release Preview.

Using a Data Template Selector

The most powerful way of dynamically changing XAML, is using a DataTemplateSelector, which allows you to choose or generate a DataTemplate based on the data object and the data-bound element. To create a template selector, create a class that inherits from DataTemplateSelector and override the SelectTemplate method. Once your class is defined you can assign an instance of the class to the template selector property of your element.

The business logic could be as simple a selecting an existing template from a resource, like this.DefaultTemplate in the following code snippet. But the entire template could also be generated on the spot through a XamlReader. The following chauvinist logic creates a colored stackpanel including a Belgian flag for superior beers, and applies the default template to the rest:

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;

public class BeerTemplateSelector : DataTemplateSelector
{
    public DataTemplate DefaultTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        Beer beer = item as Beer;

        if (!(beer.Category.Contains("Belgian") || beer.Category.Contains("Flemish")))
        {
            return this.DefaultTemplate;
        }

        BeerColorConverter converter = new BeerColorConverter();
        string backGroundColor = converter.Convert((item as Beer).Color, null, null, null).ToString();

        string template = @"
            <DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
                <StackPanel Background='" + backGroundColor + @"' Width='360'>
                    <TextBlock Text='{Binding Name}' FontWeight='Bold' Margin='2' Foreground='Black' HorizontalAlignment='center' />
                    <Grid>
                        <TextBlock Text='{Binding Category}' Margin='2' Foreground='Black' />
                        <Image Source='../Assets/belgianFlag.png' Margin='2' HorizontalAlignment='Right' Stretch='None' />
                    </Grid>
                </StackPanel>
            </DataTemplate>";
        return XamlReader.Load(template) as DataTemplate;
    }
}

Again, register the class contining the logic as a resource:

<Page.Resources>
    <DataTemplate x:Key="DefaultDataTemplate">
        <StackPanel>
            <TextBlock Text="{Binding Name}"
                        Foreground="{Binding Color, Converter={StaticResource BeerColorConverter}}"
                        FontWeight="Bold" />
            <TextBlock Text="{Binding Category}"
                        Foreground="{Binding Color, Converter={StaticResource BeerColorConverter}}" />
        </StackPanel>
    </DataTemplate>
    <local:BeerTemplateSelector x:Key="BeerTemplateSelector"
                                DefaultTemplate="{StaticResource DefaultDataTemplate}" />
</Page.Resources>

Then use an instance as ItemTemplateSelector:

<ListView 
     ItemsSource="{Binding BelgianBeers}"
     ItemTemplateSelector="{StaticResource BeerTemplateSelector}" />

With a DataTemplateSelector you're not restricted to dependency properties.

Source Code

Here's the source code of the sample project. It was written with Visual Studio 11 Express Beta, for the Windows 8 Consumer Preview: U2UConsult.Metro.DynamicTemplating.zip (70,30 kb)

Enjoy!