Page 1 of 1

WPF - Dynamically Set DataTemplate

#1 eclipsed4utoo  Icon User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1524
  • View blog
  • Posts: 5,960
  • Joined: 21-March 08

Posted 14 February 2012 - 07:35 AM

One of our members(The Architect 2.0) recently posted a topic in the WPF forum on how to make the last item in a ListBox have a different "look" than the other items.

I thought this would be a great time to do a tutorial on how to dynamically set the DataTemplate of an item. There will be very little code behind(just to populate a list for the ItemsSource of the ListBox). We will be using the DataTemplateSelector property of the ListBox.

So first, in my example, I am going to create a very, very simple class called Person, and it will have one property called FullName. This class will implement the IEquatable<T> interface to allow comparison between two Person objects.

Person.cs
class Person : IEquatable<Person>
{
    public string FullName { get; set; }

    public bool Equals(Person other)
    {
        if (other == null)
            return false;

        return (this.FullName == other.FullName);
    }
}



Next, we need to create a class the inherits from DataTemplateSelector so we can override the SelectTemplate method to use our own logic to determine the DataTemplate for the item.

public class ListBoxDataTemplateSelector : DataTemplateSelector
{

}



So now, we will override the SelectTemplate method.

public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
    FrameworkElement element = container as FrameworkElement;

    // gets the parent ListBox of the item
    var parentListBox = GetParentByType(element, typeof(ListBox));

    // gets the ItemsSource from the parent ListBox
    var itemsSource = (parentListBox as ListBox).ItemsSource.OfType<Person>();

    // gets the last item in the ItemsSource
    var lastPerson = itemsSource.Last();

    if (item != null && item is Person)
    {
        Person person = item as Person;

        // checks to see if the current item is the same as the last item
        //    in the ListBox.  If so, set the DataTemplate to show the button. 
        //    If not, show the template with the person's name
        if (person == lastPerson)
            return
                element.FindResource("LastRowTemplate") as DataTemplate;
        else
            return
                element.FindResource("PersonTemplate") as DataTemplate;
    }

    return null;
}



I commented the code, but I still wanted to run through the logic. Basically, I am going to get the last item in the ItemsSource and compare it to the current item. If they are the same(using our Equals method), then I will use the LastRowTemplate instead of the PersonTemplate.

The container that is passed to the SelectTemplate method is the ContentPresenter. To set to the parent ListBox, we need to walk up the visual tree. To do this, we can use the VisualTreeHelper.GetParent method. Since there are a number of controls between the ContentPresenter and the parent ListBox, I used a recursive method to get the parent. The method accepts the child element and the Type of the parent element that you are looking for. The method will walk up the visual tree until it either comes to the parent you are looking for, or when there is no parent for the child element.

private DependencyObject GetParentByType(DependencyObject element, Type type)
{
    // method will continue to loop through until it finds the 
    //  parent type or no parent is found.

    // no parent was found
    if (element == null) 
        return null;

    // if the types match, return it as the parent
    if (element.GetType() == type) 
        return element;

    return GetParentByType(VisualTreeHelper.GetParent(element), type);
}



Next, we will turn to our XAML. In our window.Resources section, I create a key for our DataTemplateSelector. I also create the two DataTemplates that we will need for our ListBox.

<Window x:Class="WpfApplication6.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication6"
        Title="MainWindow" Height="350" Width="525">
    
    <window.Resources>

        <local:ListBoxDataTemplateSelector
            x:Key="dataTemplateSelector" />
        
        <DataTemplate x:Key="PersonTemplate">
            <TextBlock
                Text="{Binding FullName}" />
        </DataTemplate>

        <DataTemplate
            x:Key="LastRowTemplate">
            <Button
                Content="Last Row Button"
                Click="Button_Click" />
        </DataTemplate>

    </window.Resources>
</Window>



As you can see, the DataTemplates are also very simple(I like to keep things simple). The PersonTemplate is nothing more than a TextBlock that binds to the FullName property of the item. The LastRowTemplate is simply a Button with a Click event.

Now for our ListBox

<ListBox
    ItemTemplateSelector="{Binding Source={StaticResource dataTemplateSelector}}"
    ItemsSource="{Binding}"
    Height="211"
    HorizontalAlignment="Left"
    Margin="104,70,0,0"
    Name="peopleListBox"
    VerticalAlignment="Top"
    Width="172" />



Notice that we are binding the ItemsSource property. Also, we are binding the ItemTemplateSelector to our custom selector.

Last, but not least, our very small code-behind for the window.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        ObservableCollection<Person> people = new ObservableCollection<Person>();
        people.Add(new Person() { FullName = "John Doe" });
        people.Add(new Person() { FullName = "Jane Doe" });
        people.Add(new Person() { FullName = "Ben Franklin" });
        people.Add(new Person() { FullName = "John Adams" });
        people.Add(new Person() { FullName = "" });

        peopleListBox.ItemsSource = people;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Hello from the last row");
    }
}



Here, I simply create an ObservableCollection of my Person class and set it to the ItemsSource. We also have our Click event for the button here.

So if you run the application, you should see this..

Attached Image

I have also attached the project.

Attached File  DynamicDataTemplateDemo.zip (10.64K)
Number of downloads: 399

Is This A Good Question/Topic? 3
  • +

Replies To: WPF - Dynamically Set DataTemplate

#2 The Architect 2.0  Icon User is offline

  • D.I.C Regular

Reputation: 37
  • View blog
  • Posts: 351
  • Joined: 22-May 08

Posted 16 February 2012 - 09:54 PM

Here is my 'new' situation:

I have three listboxes. Each listbox uses different 'default' datatemplates, but each one needs to have the button appended to it.

What is the best practice? Do I simply hard-code checking the name of the listbox and use a switch to return the appropriate datatemplate? Or do I create new datatemplateselectors for each listbox?

This post has been edited by The Architect 2.0: 16 February 2012 - 09:54 PM

Was This Post Helpful? 0
  • +
  • -

#3 eclipsed4utoo  Icon User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1524
  • View blog
  • Posts: 5,960
  • Joined: 21-March 08

Posted 17 February 2012 - 05:23 AM

I don't know if there is a best practice for this. Either way should work fine.
Was This Post Helpful? 1
  • +
  • -

Page 1 of 1