Prism 4 Gems: Rendering Heterogeneous Collections with DataTemplateSelector

Prism 4 has a lot to offer in terms of its primary features, which includes UI Composition (Regions), Modularity, Pub/Sub events, Commands, Navigation, and MVVM guidance. But there are also a number of helpful little chunks of code included that you can use or copy even if you are not using the rest of Prism.

One of those that I want to show you how to use in this post is the DataTemplateSelector class. DataTemplates in WPF have a very important feature: the DataType property that allows DataTemplates to be automatically applied to a data bound object based on its type. This feature is very similar to the implicit styles capability that WPF has had since the start that is a new feature in Silverlight 4.

Unfortunately Silverlight is still lacking this feature. In Silverlight, you have to associated a single data template with a data bound control through its template property (ItemTemplate for ItemsControl and ContentTemplate for ContentControl). When you want to bind to a heterogeneous collection of objects (that all share a common base class or interface that the collection is defined in terms of), you are kind of stuck in Silverlight if you want to write specific templates per derived type.

DataTemplateSelector from Prism comes to the rescue for this scenario however. The definition of DataTemplateSelector is kind of strange – it is a ContentControl derived class that amounts to a switch-case statement you can put in your XAML to select the right data template based on the actual type of the data object it is being rendered for.

So as a simple example, say that you had some base class that a number of different derived classes were going to inherit from. Each derived class was going to define its own set of properties that need to be presented and bound to controls when that type is encountered in a collection of the base class type.

So with a simple base type like the following:

   1: public class DataTypeBase { }

We could define two derived types like the following:

   1: public class DataType1 : DataTypeBase

   2: {

   3:     public int ID { get; set; }

   4:     public string Name { get; set; }

   5: }

   6:  

   7: public class DataType2 : DataTypeBase

   8: {

   9:     public int Index { get; set; }

  10:     public string Description { get; set; }

  11: }

Then if we were going to present items of that type in a ListBox, ComboBox or other ItemsControl-based control, we would define the data templates we wanted to use to render them out:

   1: <DataTemplate x:Key="DataType1">

   2:     <StackPanel Orientation="Horizontal">

   3:         <TextBlock Text="{Binding ID}"/>

   4:         <toolkit:Separator />

   5:         <TextBlock Text="{Binding Name}" />

   6:     </StackPanel>

   7: </DataTemplate>

   8:  

   9: <DataTemplate x:Key="DataType2">

  10:     <StackPanel Orientation="Horizontal">

  11:         <TextBox Text="{Binding Index}" />

  12:         <toolkit:Separator />

  13:         <TextBox Text="{Binding Description}" />

  14:     </StackPanel>

  15: </DataTemplate>

 

This is all just simple, basic Silverlight (and WPF) data binding concepts so far.

Now say we want to expose a collection from a view model that allows us to put instances of both DataType1 and DataType2 in it. We would just define a collection in terms of the base class:

   1: public class MainPageViewModel

   2: {

   3:     public MainPageViewModel()

   4:     {

   5:         Data = new ObservableCollection<DataTypeBase>();

   6:         // Hard code some items for demo purposes

   7:         Data.Add(new DataType1 { ID = 42, Name = "Fred" });

   8:         Data.Add(new DataType2 { Index = 33, Description = "This is a different data type" });

   9:     }

  10:     public ObservableCollection<DataTypeBase> Data { get; set; }

  11: }

 

Now the only problem in Silverlight is how to associate those data templates applied. If there were only going to be a single type or a single rendering of the objects in the bound control, you could just set a single data template through the ItemTemplate property:

   1: <ListBox ItemsSource="{Binding Data}"

   2:          ItemTemplate="{StaticResource MyDataTemplate}" />

 

But we want both data templates defined above to be applied to their respective types, which will be in the bound collection. This is where the DataTemplateSelector comes in.

The way you use it is to define a DataTemplate (typically as a resource at the current time because of this bug that causes memory leaks if you define a DataTemplate nested under the element). Inside the DataTemplate, you create an instance of the DataTemplateSelector. Then you simply nest the multiple DataTemplates you want to apply by type inside the DataTemplateSelector’s resources:

   1: <UserControl.Resources>

   2:     <DataTemplate x:Key="SelectorDataTemplate">

   3:         <prism:DataTemplateSelector Content="{Binding}"

   4:                                     HorizontalContentAlignment="Stretch"

   5:                                     IsTabStop="False">

   6:             <prism:DataTemplateSelector.Resources>

   7:                 <DataTemplate x:Key="DataType1">

   8:                     <StackPanel Orientation="Horizontal">

   9:                         <TextBlock Text="{Binding ID}"/>

  10:                         <toolkit:Separator />

  11:                         <TextBlock Text="{Binding Name}" />

  12:                     </StackPanel>

  13:                 </DataTemplate>

  14:  

  15:                 <DataTemplate x:Key="DataType2">

  16:                     <StackPanel Orientation="Horizontal">

  17:                         <TextBox Text="{Binding Index}" />

  18:                         <toolkit:Separator />

  19:                         <TextBox Text="{Binding Description}" />

  20:                     </StackPanel>

  21:                 </DataTemplate>

  22:  

  23:             </prism:DataTemplateSelector.Resources>

  24:         </prism:DataTemplateSelector>

  25:     </DataTemplate>

  26: </UserControl.Resources>

 

You can see that because the DataTemplateSelector is a ContentControl, it can be the single root element of a DataTemplate, and an “instance” of it will be rendered out for each item in the collection it is used with. However, the DataTemplateSelector does not really have any visual appearance of its own. What it does is render out one of the DataTemplates defined in its Resources collection, selecting the appropriate one based on the data type of the object that is the DataContext.

So the steps are as follows:

  • Define a DataTemplate in your Resources and give it a key.
  • Declare a Prism DataTemplateSelector as the element of the DataTemplate.
  • Set its Content by binding to the DataContext that flows into it (Content={Binding}).
  • Add individual DataTemplates in the Resources collection of the DataTemplateSelector
  • Set the x:Key for each nested DataTemplate to the Type name of the data object you want it to be used for.
  • Set the root DataTemplate as the ItemTemplate on a control bound to a heterogeneous collection of those types.

Simple, straightforward, works great. You can download a full sample here.

Enjoy!

Comments are closed.