Introduction
This article reviews a class which allows you to move template selection logic out of DataTemplateSelector subclasses. Using this technique allows you to encapsulate knowledge of DataTemplate resource keys into the places that actually contain those resources. It also makes it easier to implement template selection logic which requires more information about the state of the application than is typically available within a DataTemplateSelector subclass. This technique can vastly reduce the number of template selector classes in a Windows Presentation Foundation (WPF) application, thus making it easier to extend and maintain.
Background
WPF controls often provide a means of programmatically selecting a DataTemplate with which to render a data object. This functionality is exposed via properties whose names are suffixed with "TemplateSelector". Some examples of this include the ContentTemplateSelector property of ContentControl, and ItemTemplateSelector of ItemsControl. Template selectors are classes which derive from DataTemplateSelector and override the SelectTemplate method.
The problem
Typically "template selector" classes end up containing hard-coded resource keys, for example:
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(
object item, DependencyObject container )
{
FrameworkElement elem = container as FrameworkElement;
Foo foo = item as Foo;
if( foo.Name == "Cowabunga" )
return elem.FindResource( "SomeDataTemplate" );
else
return elem.FindResource( "SomeOtherDataTemplate" );
}
}
This is not always a desirable way to implement such logic. A DataTemplateSelector cannot contain its own resources, so the DataTemplates it references are always defined in the Resources collection of some other element. Referencing templates in a template selector duplicates knowledge of the resource keys. Duplicating information is generally a bad practice. If a DataTemplate's resource key is changed, a new template is introduced, or an existing template is removed, then the template selector class must be updated accordingly. It would be better if template selectors were not dependent upon specific resource keys, so that they were not so tightly coupled to the elements which use them.
Template selectors are not always the ideal place to implement certain types of template selection logic. In some situations it is necessary to know the state of other elements in the user interface (UI) in order to determine which template should be used. The code which executes within a template selector's SelectTemplate method has no direct visibility into other parts of the UI. It is sometimes necessary for template selection logic to know more than a template selector can know on its own.
The solution
My solution to this problem is to let the template selector delegate its job to another part of the application better equipped to determine which DataTemplate to use. I created the RoutedDataTemplateSelector class to do exactly that. The basic idea is that when a DataTemplate needs to be selected, the RoutedDataTemplateSelector bubbles an event up the element tree, starting at the element which requires the template. Whoever handles that event can determine the template to be used.
Using RoutedDataTemplateSelector prevents a large number of DataTemplateSelector subclasses from popping into existence, each with hard-coded resource keys. Instead you can embed the template selection logic into the Window/Page/UserControl which contains both the DataTemplates to choose from and element being templated. The end result of using this approach is that changing an element's resources does not have a large ripple effect throughout your code base, and your template selection logic has more runtime context to work with.
Using the RoutedDataTemplateSelector
Suppose that we use an ItemsControl to display a list of Person objects, and we want the items in the list to display alternating background colors. We could achieve this by applying two DataTemplates to the items in the list, switching between the templates for each consecutive item. One template renders an item with one color and the other template renders an item with a different color. It might look like this:

We can easily implement this functionality by using the RoutedDataTemplateSelector, as seen in the abridged example below.
<Window ... >
<Window.Resources>
<DataTemplate x:Key="PersonTemplateEven">
<Border ... >
<TextBlock Text="{Binding Path=Name}" Background="LightBlue" />
</Border>
</DataTemplate>
<DataTemplate x:Key="PersonTemplateOdd">
<Border ... >
<TextBlock Text="{Binding Path=Name}" Background="WhiteSmoke" />
</Border>
</DataTemplate>
<jas:RoutedDataTemplateSelector x:Key="PersonTemplateSelector" />
</Window.Resources>
<Grid>
<ItemsControl
x:Name="personList"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource PersonTemplateSelector}"
Margin="3"
jas:RoutedDataTemplateSelector.TemplateRequested="OnTemplateRequested"
/>
</Grid>
</Window>
The ItemsControl markup seen above uses the "attached event" syntax to specify what method should be invoked when the RoutedDataTemplateSelector's TemplateRequested routed event is raised on it. The method which determines what DataTemplate to apply to the Person object is in the code-behind file of the Window, as seen below:
void OnTemplateRequested( object sender, TemplateRequestedEventArgs e )
{
Person person = e.DataObject as Person;
ItemContainerGenerator generator = this.personList.ItemContainerGenerator;
DependencyObject container = generator.ContainerFromItem( person );
int visibleIndex = generator.IndexFromContainer( container );
string templateKey =
visibleIndex % 2 == 0 ?
"PersonTemplateEven" :
"PersonTemplateOdd";
e.TemplateToUse = this.FindResource( templateKey ) as DataTemplate;
e.Handled = true;
}
As this example demonstrates, the logic which selects a template to use is located in the Window which contains the ItemsControl being templated. This allows the template resource keys to only be known by the Window which owns them, and makes it easy to figure out what color the templated item should be. If this logic was in a template selector then it would be brittle, and more difficult to determine at what index the item exists in the control.
How it works
RoutedDataTemplateSelector is not a very complicated class. It is a DataTemplateSelector subclass which exposes a bubbling routed event named TemplateRequested. When the overridden SelectTemplate method is invoked, it raises that event on the element to be templated and expects an ancestor in its logical tree to specify the DataTemplate to return. That class is seen below:
public class RoutedDataTemplateSelector : DataTemplateSelector
{
public static readonly RoutedEvent TemplateRequestedEvent =
EventManager.RegisterRoutedEvent(
"TemplateRequested",
RoutingStrategy.Bubble,
typeof( TemplateRequestedEventHandler ),
typeof( RoutedDataTemplateSelector ) );
[EditorBrowsable( EditorBrowsableState.Never )]
public event TemplateRequestedEventHandler TemplateRequested
{
add
{
throw new InvalidOperationException(
"Do not directly hook the TemplateRequested event." );
}
remove
{
throw new InvalidOperationException(
"Do not directly unhook the TemplateRequested event." );
}
}
public override DataTemplate SelectTemplate(
object item, DependencyObject container )
{
UIElement templatedElement = container as UIElement;
if( templatedElement == null )
throw new ArgumentException(
"RoutedDataTemplateSelector only works with UIElements." );
TemplateRequestedEventArgs args =
new TemplateRequestedEventArgs(
TemplateRequestedEvent, templatedElement, item );
templatedElement.RaiseEvent( args );
return args.TemplateToUse;
}
}
The one oddity about this class is that it exposes a CLR wrapper event for the TemplateRequested routed event, but using it will cause an exception to be thrown. That wrapper event declaration exists so that the compiler does not report an error when trying to assign a handler to TemplateRequested in XAML. Since DataTemplateSelector does not derive from UIElement it does not have the AddHandler and RemoveHandler methods typically used to manage routed events. What that means in practical terms is that if you execute this code an exception will be thrown:
RoutedDataTemplateSelector selector = new RoutedDataTemplateSelector();
selector.TemplateRequested += this.OnTemplateRequested;
Instead you should use this approach:
someElement.AddHandler(
RoutedDataTemplateSelector.TemplateRequestedEvent,
new TemplateRequestedEventHandler( this.OnTemplateRequested ) );
History
- May 13, 2007 – Created article