Home > .NET > Dynamically Switch WPF DataTemplate

Dynamically Switch WPF DataTemplate

January 9, 2009

This post is based on this thread I started at MSDN Forums.  You can read the original situation and questions there: this post is more of a generic “how to”.

The Problem

In my original scenario I had a simple DataTemplate for ListBoxItems.  When an Item was Selected, I wanted to use a different DataTemplate that had more details.  The Binding was the same for each, so it seemed to me that I should simply be able to switch the DataTemplates at will.  Unfortunately, this is not the case.  I attempted to pull the selected item from the ListBox on the SelectionChanged event and assign it a different DataTemplate, but it did not work.  It still seems to me that it would be possible, but my efforts failed and I couldn’t find anything similar online.

The Solution

Since I couldn’t simply apply the DataTemplate resource to the SelectedItem, I had to find a different way.  After reading many suggestions for how to accomplish this, I settled on using a DataTemplateSelector.  This is a custom class with a method that returns a DataTemplate based on whatever criteria you need.  The custom DataTemplateSelector is then assigned to the element: in the case of ListBox it is the ItemTemplateSelector property.

I had previously read about DataTemplateSelector, but I was under the mistaken impression that it was used only in the context of state changes for the bound object.  This may have been because every example I found online that uses the selector approach was written with that scenario in mind.  It finally occurred to me, however, that I could access the Application state as well, which means that I could pull basically any information I needed, such as the currently selected item in my ListBox.

OK, so enough background: let’s get to how to do this.  Feel free to download the demo project for this post.

Create the Data Templates

There is nothing special about the DataTemplates themselves, so just create them as you normally would.  In the sample project, I have created a simple template and a complex template.  We’ll assign the complex template to the SelectedItem and the simple template to all the others.

Create the DataTemplateSelector

This is where the magic happens.  The code will process whenever a DataTemplateSelector instance has been assigned as well as everytime the DataSource changes.  I’ll get back to that in a minute, but for now you need to create your custom DataTemplateSelector class by extending DataTemplateSelector and overriding the SelectTemplate method:

public class PersonDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item != null && item is Person)
        {
            Person person = item as Person;
            Window window = System.Windows.Application.Current.MainWindow;
            ListBox list = window.FindName("PeopleListBox") as ListBox;

            Person selectedPerson = list.SelectedItem as Person;
            if (selectedPerson != null && selectedPerson.FullName() == person.FullName())
            {
                return window.FindResource("PeopleTemplateComplex") as DataTemplate;
            }
            else
            {
                return window.FindResource("PeopleTemplateSimple") as DataTemplate;
            }
        }

        return null;
    }
}

A few things to note:

  • the first parameter coming in is an object, so we need to test it to make sure it is the object type we expect.  Once we do that, we can then cast it to a usable instance of that type.
  • We use WPF’s knowledge of the Application to get a reference to the MainWindow where our ListBox is defined.  I then use the FindName method to get a reference to the ListBox.  This approach basically makes the entire application open to your selector instance.
  • Finally, we get a refence to the appropriate DataTemplate and return it.

So in the case of our ListBox, this method will be called for each ListBoxItem.  If no items are selected, then all of them will apply the simple template.

Assign the DataTemplateSelector

Now that we have our selector created, we need to use it in our XAML.  The first step towards this is to add a reference to our Window for the local Namespace.  I find it a good practice when I create the project to just automatically add this reference so I always have access to any local classes I may create, like ValueConverters.  Here is the line in the demo project that does this:

xmlns:prog="clr-namespace:DemoDataTemplateSelector"

Now we need to create an instance of our custom DataTemplateSelector in Window.Resources, like so:

<prog:PersonDataTemplateSelector x:Key="personDataTemplateSelector" />

Now we need to add the ItemTemplateSelector property to our ListBox and reference the object we created:

ItemTemplateSelector="{StaticResource personDataTemplateSelector}"

Finish the Code

If you run the application at this point, the simple template will be applied as selected, but the template will not change when an item is selected.  Above I said that the selector code would execute whenever the DataSource changes: selecting an item in the ListBox does not alter the DataSource, so the selector code is not activated.  The way around this is to create a new instance of the selector object and assign it programmatically to the ListBox.  In this case, we want it to execute when the SelectionChanged event fires:

private void PeopleListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    PeopleListBox.ItemTemplateSelector = new PersonDataTemplateSelector();
}

Now if you run the code it should work as desired, changing the DataTemplate from simple to complex for the selected item.

An Alternative Approach

Another approach posted here was to use a single DataTemplate containing a Trigger to switch them out.  The trick in my scenario was to figure out how to fire the trigger when the selected item changes.  I found a post at StackOverflow that demonstrated this well.  Merging these two approaches together achieved the result I wanted.  Here are a few key elements to this approach:

  • Change the original DataTemplates to ControlTemplates.
  • Create a new DataTemplate containing a single Control element that defines the default Template.
  • Add a DataTrigger that binds the RelativeSource to the Ancestor ListBox’s IsSelected property = true.
  • On the ListBox itself, instead of using the ItemTemplateSelector property, set the ItemTemplate property to the new DataTemplate.

Learning this method really opened my eyes to the power of DataTriggers.  I shied away from this at first because the XAML is more complex, and I’m still in the adolescent stages of XAML knowledge.  But having implemented it, there are definitely some benefits.  Using the DataTrigger approach requires a lot less coding: I do not have to create a custom DataTemplateSelector class; I do not need ListBox.SelectionChanged event; I do not need to declare the DataTemplateSelector instance in my XAML.  Since it can be argued that this is a purely UI problem, so keeping as much of it as possible in the XAML does a better job of separation of responsibilities.  Which means that in a scenario where designers really are separate from developers, the designer does not need to rely on the developer to achieve this feature.

Conclusion

Working through this has taken me a lot farther down the road of understanding binding, templates, and triggers.  And of course, both approaches are perfectly valid (so the sample code includes both approaches.)  I think for simple situations, triggers win hands down.  But for more complex scenarios, such as needing to base the DataTemplate selection on multiple factors, or processing more than two DataTemplates, then the custom DataTemplateSelector offers more flexibility and options.  In either case, this is one tool in my toolbox that I think I’ll be using a lot.

Advertisement
Categories: .NET
  1. Molon Labe
    January 9, 2009 at 6:43 pm

    Maybe the container argument has the information you seek out of the ListBox? Wouldn’t CType(container,ListBoxItem).DataContext be the Person object bound to the ListBoxItem? Then you wouldn’t have to dig through Application to find a hardcoded element name.

  2. Molon Labe
    January 12, 2009 at 4:15 pm

    Ah, nevermind. The item is already the Person. You need to get the selected person. You could still use ItemsControl.ItemsControlFromItemContainer(container) to get to the ListBox.

    Also, ListBoxItem (the container) has an IsSelected property which you could test for instead of retrieving the SelectedItem from the ListBox.

  3. January 12, 2009 at 5:49 pm

    I tried that, but container is actually a ContentPresenter and not the containing element. And when I tried to find the ContentPresenter’s parent, it too was listed as a ContentPresenter. When I issued “var p = ItemsControl.ItemsControlFromItemContainer(container);”, p was returned null.

    It does seem to me that there should be a way to make this work without hard coding any values, but I’m not sure what it would be (other than storing the hard coded strings as variables).

  4. Shimmy
    September 8, 2009 at 10:46 pm

    There is a better way to do it with Triggers in pure Xaml

  5. September 9, 2009 at 9:43 am

    Thanks Shimmy, the pure XAML approach is mentioned above in the section titled “An Alternative Approach”. Please feel free to post some code if you have something better.

  6. ursri
    October 7, 2009 at 7:41 am

    This works fine with WPF win application. But the same code fails for Sketchflow
    Can anyone throw some light on how to set datatemplate to selected list item in Sketchflow screen?

  7. November 11, 2009 at 6:46 am

    Quoting: –” I had previously read about DataTemplateSelector, but I was under the mistaken impression that it was used only in the context of state changes for the bound object. “– … –” that I could access the Application state as well, which means that I could pull basically any information I needed, such as the currently selected item in my ListBox.”–

    that was the point! and your solution is a great idea, that solved my problem. Thanks!

  8. November 11, 2009 at 10:33 am

    That’s great Mauro! Make sure you download the project and check out the Trigger approach: since I wrote that article, I’ve come to find it is easier to implement in most situations.

  9. AvA
    February 16, 2010 at 11:59 am

    Window window = System.Windows.Application.Current.MainWindow

    The problem is, that my MainWindow is NOT the window where I have my comboBox in. My mainWindow opens another window, say winXXX; this window is the active window and has the comboBox in it.
    My DataTemplateSelector still selects the MainWindow.
    How can I change this?

  10. Marty
    April 7, 2011 at 4:34 pm

    Hi Joel, nice post.
    You can get the containing ListBox by using the ItemsControl.ItemsControlFromItemContainer(container) method. The problem is that the “container” has to be a ListBoxItem. I achieved this in Silverlight by using VisualTreeHelper.GetParent recursively on the container object (should work in WPF as well, but not sure).
    Btw.: ListBoxItem has a IsSelected property.

    Also you could expose two dependency properties for the DataTemplates, that way they can be set directly from XAML or by using a Binding.

  11. abhi
    July 27, 2012 at 3:39 am

    Your sample project for download link is throwing a 404. Kindly restore the link.

    • February 8, 2013 at 7:38 am

      Please read the last post on this blog from Sept 2012:

  1. No trackbacks yet.
Comments are closed.
%d bloggers like this: