Flexible Data Template Support in Silverlight

WPF has a great feature called data templates. These allow you to specify the visual appearance for a data object, you can either place them in a control or put them in a resources section to reuse them for multiple controls.

The real benefit in my opinion of them is that you can tag them with the type of the data object that they display, WPF will then automatically select the appropriate data template when it needs to render an item of that type. This makes building controls that display heterogeneous data structures really easy.

I was recently working on a Silverlight application in which I had a 3 pane view, one of the panes contained the main content and the other two contained navigation elements in a typical master / details type scenario. The items in the navigation pane were are of different types all deriving from a common base class. When I selected the item in the navigation pane I wanted to display the details in the main pane, however since each item was of a different type they needed to display differently in the main pane.

Fantastic I thought, I can use data templates and this’ll be easy. That was when I found out that Silverlight doesn’t support data templates that vary by type. You can define them and share them using a named key but unfortunately you cannot assign a target type to them like you can in WPF.

This wouldn’t be much of a blog article unless I’d found a way around this though so here we go. There are probably several different ways you could do this but this is the way I chose and it seems to work quite nicely, at least for me.

Bring on the converter

What I thought was I can databind the data object to the data template property of the control and implement a converter that yields a data template based on the type of the data object it receives. Ignoring the loading of the resources you can see the code below takes the type of the value passed into the converter and then looks it up in a map to find out the template to use which is then returned.

Convert() method

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if(LoadFromApplicationResources && !_loaded)
        LoadFromResources();

    if (value != null)
    {
        Type valueType = value.GetType();

        return (from template in TemplateMap
                where template.SourceType == valueType.FullName
                select template.DataTemplate).FirstOrDefault();
    }

    return null;
}

This allows the XAML to look like this. The content control for the main pane binds the content to the current item in my data context (which is a view model), this item has a child property called Item that I want to use to choose the type of data template to use. I then use the converter MainItemTemplates to convert the CurrentItem.Item into a data template for the content control.

Using the converter

<ContentControl Content="{Binding CurrentItem}"
                ContentTemplate="{Binding CurrentItem.Item,Converter={StaticResource MainItemTemplates}}"
                />

Declaring the converter in the resources then looks like this. The converter declares (in this case) 4 mappings from various source types into various data templates previously defined in the resources. These data templates are defined as regular keyed resource data templates.

Declaring the converter

<converters:TemplateSelectorConverter x:Key="MainItemTemplates">
    <converters:TemplateMapEntry SourceType="DataItems.QuestionItem" DataTemplate="{StaticResource QuestionItemTemplate}" />
    <converters:TemplateMapEntry SourceType="DataItems.ServiceItem" DataTemplate="{StaticResource ServiceItemTemplate}" />
    <converters:TemplateMapEntry SourceType="DataItems.DownloadItem" DataTemplate="{StaticResource DownloadItemTemplate}" />
    <converters:TemplateMapEntry SourceType="DataItems.LinkItem" DataTemplate="{StaticResource LinkItemTemplate}" />
</converters:TemplateSelectorConverter>

Using attached properties

The above approach is nice since it allows you to have multiple mappings of types to data templates and also allows you to have the data template selector something different from the item being data bound to the content control. One downside to it is that you have to build and maintain the map though.

In order to get a more WPF like effect I introduced an attached property with some behaviour as shown below. I then look for any data templates in the application resources that have this property attached and pre-populate the map from them.

The attached property

public static readonly DependencyProperty SourceTypeProperty =
            DependencyProperty.RegisterAttached("SourceType", typeof(string), typeof(TemplateSelectorConverter),
                                                new PropertyMetadata(null));

GetSourceType() is the get method for the attached property declared above. Currently GetDataTemplates() only looks in the application resources but later it could check all of the resources in the application.

Prepopulating the map

private void LoadFromResources()
{
    _loaded = true;

    var dataTemplates =
        from resource in GetDataTemplates()
        let sourceType = GetSourceType(resource)
        where !string.IsNullOrEmpty(sourceType)
        select new TemplateMapEntry
               {
                   SourceType = sourceType,
                   DataTemplate = resource,
               };

    TemplateMap.AddRange(dataTemplates);
}

private static IEnumerable<DataTemplate> GetDataTemplates()
{
    return Application.Current.Resources.Values.OfType<DataTemplate>();
}

And (almost) finally to use the attached property in the XAML on a data template.

Using the attached property

<DataTemplate x:Key="QuestionItemTemplate"
              converters:TemplateSelectorConverter.SourceType=
              "DataItems.QuestionItem">

Then declare the converter and tell it to load the map from application resources.

Declaring the converter

<converters:TemplateSelectorConverter x:Key="MainItemTemplates" LoadFromApplicationResources="True"/>

Conclusion

This is just one way of adding support for data templating heterogeneous data structures in Silverlight but it worked quite well for me. Any comments or suggestions how to improve it are welcome.

You can download the sample source for the converter from http://garfoot.com/samples/TemplateSelectorConverter.zip. Please note this is sample code so treat it as such.

No related posts.

2 Comments

  1. Andris says:

    Hi, Rob!

    I try create combo box with different items’ lookup (depending of model type). Data source for combo box is collection of ViewModels and I use described TemplateSelector to select data template for items. Each model have property called "PropertyType", which should be used for choosing DataTemplates. The problem is:

    !!! only first DataTemplate is taken correctly and this DataTemplate is used in all other items, however models have different "PropertyType" values.

    Firs try:
    <ComboBox x:Name="cmbTest" ItemsSource="{Binding ViewModels}" ItemTemplate="{Binding Path=PropertyType, Converter={StaticResource TemplateSelectorConverter}}"/>

    Second try:
    <ComboBox x:Name="cmbTest" ItemsSource="{Binding ViewModels}" >
    <ComboBox.ItemTemplate>
    <DataTemplate>
    <ContentControl Content="{Binding}" ContentTemplate="{Binding Path=PropertyType, Converter={StaticResource TemplateSelectorConverter}}"/>
    </DataTemplate>
    </ComboBox.ItemTemplate>
    </ComboBox>

    Can you suggest something? Maybe I’m wrong in my code?
    Regards, Andris

  2. admin says:

    Thanks for the comment Andris. I’ve not tried using it with a combo box so it’s possible that there is a problem with the code. I mainly used it to display content areas on a page differently depending what was bound to it.

    I’ll have a look at it in the next few days if I get chance to see if I can see what’s up.

Leave a Reply