Share via


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

  1. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

  2. {

  3.     if(LoadFromApplicationResources && !_loaded)

  4.         LoadFromResources();

  5.     if (value != null)

  6.     {

  7.         Type valueType = value.GetType();

  8.         return (from template in TemplateMap

  9.                 where template.SourceType == valueType.FullName

  10.                 select template.DataTemplate).FirstOrDefault();

  11.     }

  12.     return null;

  13. }

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

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

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

  1. <converters:TemplateSelectorConverter x:Key="MainItemTemplates">
  2.     <converters:TemplateMapEntry SourceType="DataItems.QuestionItem" DataTemplate="{StaticResource QuestionItemTemplate}" />
  3.     <converters:TemplateMapEntry SourceType="DataItems.ServiceItem" DataTemplate="{StaticResource ServiceItemTemplate}" />
  4.     <converters:TemplateMapEntry SourceType="DataItems.DownloadItem" DataTemplate="{StaticResource DownloadItemTemplate}" />
  5.     <converters:TemplateMapEntry SourceType="DataItems.LinkItem" DataTemplate="{StaticResource LinkItemTemplate}" />
  6. </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

  1. public static readonly DependencyProperty SourceTypeProperty =
  2.             DependencyProperty.RegisterAttached("SourceType", typeof(string), typeof(TemplateSelectorConverter),
  3.                                                 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.

Pre-populating the map

  1. private void LoadFromResources()

  2. {

  3.     _loaded = true;

  4.     var dataTemplates =

  5.         from resource in GetDataTemplates()

  6.         let sourceType = GetSourceType(resource)

  7.         where !string.IsNullOrEmpty(sourceType)

  8.         select new TemplateMapEntry

  9.                {

  10.                    SourceType = sourceType,

  11.                    DataTemplate = resource,

  12.                };

  13.     TemplateMap.AddRange(dataTemplates);

  14. }

  15. private static IEnumerable<DataTemplate> GetDataTemplates()

  16. {

  17.     return Application.Current.Resources.Values.OfType<DataTemplate>();

  18. }

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

Using the attached property

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

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

Declaring the converter

  1. <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 https://garfoot.com/samples/TemplateSelectorConverter.zip. Please note this is sample code so treat it as such

Originally posted by Rob Garfoot on 26 March 2010 https://www.garfoot.com/blog/archive/2010/03/26/flexible-data-template-support-in-silverlight.aspx