Click here to Skip to main content
6,593,923 members and growing! (12,284 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » Templates     Intermediate License: The Code Project Open License (CPOL)

Routed Template Selection in WPF

By Josh Smith

Examines a powerful technique for implementing DataTemplate selection logic.
C# 2.0, Windows, .NET 3.0, XAML, WPF, VS2005, Dev
Posted:13 May 2007
Views:37,547
Bookmarked:34 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
18 votes for this article.
Popularity: 5.70 Rating: 4.54 out of 5
1 vote, 5.6%
1

2

3
2 votes, 11.1%
4
15 votes, 83.3%
5

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 )
{
 // Get a reference to the Person object being templated.

 Person person = e.DataObject as Person;      

 // This is one way to create "alternate row colors" in an ItemsControl.

 ItemContainerGenerator generator = this.personList.ItemContainerGenerator;
 DependencyObject container = generator.ContainerFromItem( person );
 int visibleIndex = generator.IndexFromContainer( container );
 string templateKey = 
  visibleIndex % 2 == 0 ? 
  "PersonTemplateEven" : 
  "PersonTemplateOdd";

 // Specify the data template which should be used to render

 // the Person object.

 e.TemplateToUse = this.FindResource( templateKey ) as DataTemplate;

 // Mark the event as "handled" so that it stops bubbling up 

 // the element tree.

 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
{
 /// <summary>

 /// Represents the TemplateRequested bubbling routed event.

 /// </summary>

 public static readonly RoutedEvent TemplateRequestedEvent =
  EventManager.RegisterRoutedEvent(
   "TemplateRequested",
   RoutingStrategy.Bubble,
   typeof( TemplateRequestedEventHandler ),
   typeof( RoutedDataTemplateSelector ) );

 // This event declaration is only here so that the compiler allows

 // the TemplateRequested event to be assigned a handler in XAML.

 // Since DataTemplateSelector does not derive from UIElement it 

 // does not have the AddHandler/RemoveHandler methods typically

 // used within an explicit event declaration.

 [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." ); 
  }
 }

 /// <summary>

 /// Raises the TemplateRequested event up the 'container' element's logical

 /// tree so that the DataTemplate to return can be determined.

 /// </summary>

 /// <param name="item">The data object being templated.</param>

 /// <param name="container">The element which contains the data.</param>

 /// <returns>The DataTemplate to apply.</returns>

 public override DataTemplate SelectTemplate( 
    object item, DependencyObject container )
 {   
  // We need 'container' to be a UIElement because that class

  // exposes the RaiseEvent method.

  UIElement templatedElement = container as UIElement;
  if( templatedElement == null )
   throw new ArgumentException( 
    "RoutedDataTemplateSelector only works with UIElements." );

  // Bubble the TemplateRequested event up the logical tree, starting at the

  // templated element. This allows others to determine what template to use.

  TemplateRequestedEventArgs args = 
    new TemplateRequestedEventArgs( 
     TemplateRequestedEvent, templatedElement, item );

  templatedElement.RaiseEvent( args );

  // Return the DataTemplate selected by the outside world.

  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:

// This does not work!

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Josh Smith


Member
Josh creates software, mostly with C# and XAML.

He works at IdentityMine as a Senior UX Developer.

He plays the music of J.S. Bach on the piano, but has started branching into other composers recently.

Get his runtime debugging and scripting tool, called Crack.NET, right here[^].

Download his WPF.JoshSmith library here[^]

You can check out his WPF blog here[^].

You can take his guided tour of WPF here[^].

You can check out a powerful debugger visualizer he worked on called Mole for Visual Studio here[^].

His Microsoft MVP profile can be viewed here[^].
Occupation: Software Developer (Senior)
Company: IdentityMine, Inc.
Location: United States United States

Other popular Windows Presentation Foundation articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 28 (Total in Forum: 28) (Refresh)FirstPrevNext
GeneralThanks PinmemberAEgiomo0:37 29 Jun '09  
GeneralVS 2008 XAML editor does not load Window1.xaml PinmemberTerry7606:21 30 Apr '08  
GeneralRe: VS 2008 XAML editor does not load Window1.xaml PinmvpJosh Smith15:01 4 May '08  
GeneralThanks again PinmemberKavan Shaban20:41 18 Apr '08  
QuestionAnyway to apply this to a HierarchicalDataTemplate's ItemTemplateSelector? PinmemberScott Williams5:47 26 Mar '08  
GeneralNevermind, I'm a doofus. PinmemberScott Williams11:48 26 Mar '08  
GeneralRe: Nevermind, I'm a doofus. PinmvpJosh Smith11:51 26 Mar '08  
GeneralStyle Selection Pinmembergmarappledude2:51 24 Oct '07  
GeneralRe: Style Selection PinmvpJosh Smith3:17 24 Oct '07  
GeneralRe: Style Selection Pinmembergmarappledude3:26 24 Oct '07  
GeneralHow to cause templates to be re-selected after a state change PinmemberDrew Noakes3:21 1 Aug '07  
GeneralRe: How to cause templates to be re-selected after a state change PinmvpJosh Smith3:58 1 Aug '07  
GeneralRe: How to cause templates to be re-selected after a state change PinmemberDrew Noakes6:06 1 Aug '07  
GeneralRe: How to cause templates to be re-selected after a state change PinmvpJosh Smith6:34 1 Aug '07  
GeneralThank You Pinmemberjellodog8:19 11 Jul '07  
GeneralRe: Thank You PinmvpJosh Smith8:32 11 Jul '07  
GeneralEvent Triggers throws an exception if I set my own Routed event Pinmembermarlongrech23:09 24 May '07  
GeneralRe: Event Triggers throws an exception if I set my own Routed event PinmvpJosh Smith0:37 25 May '07  
QuestionAnother way? Pinmembermvtongeren1:03 23 May '07  
AnswerRe: Another way? PinmvpJosh Smith3:22 23 May '07  
GeneralRe: Another way? Pinmembermvtongeren2:29 24 May '07  
GeneralHoly Cow PinmemberSacha Barber3:21 14 May '07  
GeneralRe: Holy Cow PinmvpJosh Smith3:50 14 May '07  
GeneralRe: Holy Cow PinmemberSacha Barber5:10 14 May '07  
GeneralRe: Holy Cow PinmemberSacha Barber8:02 15 May '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 13 May 2007
Editor: Deeksha Shenoy
Copyright 2007 by Josh Smith
Everything else Copyright © CodeProject, 1999-2009
Web17 | Advertise on the Code Project