65.9K
CodeProject is changing. Read more.
Home

WPF Based Dynamic DataTemplateSelector

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (7 votes)

Jul 9, 2012

CPOL

4 min read

viewsIcon

123452

downloadIcon

2787

Demonstrates how to make use of a completely WPF based DataTemplateSelector relying on no code-behind.

Introduction

This article will cover a method for defining different DataTemplateSelector options within your WPF code and allowing a very small amount of backend code to take care of all the selections for you. This allows you to make use of this code from within an MVVM structure without any issues, and while making the front end extremely easy to manage without complicating the back end code.

Background

Recently I was working on a project in which I wanted to implement a list where the contents could be one of a number of different types. Obviously, I wouldn't know what the contents of this list were beforehand (and as such I couldn't just statically create the content) so I started looking at ways to bend the ItemsTemplate property to do my nefarious bidding. One of the first things I noticed was ItemsTemplateSelector which sounded promising, but upon reading up a bit on it I found that it encouraged the use of code behind to select the data templates.

The way you are expected to do this, for those of you who are curious and a bit too lazy to go looking elsewhere for an example, is documented below.

DataTemplate Usage

WPF Code

<local:TemplateSelector x:Key="MyTemplateSelector" />

<Style x:Key="MyListStyle" TargetType="ListView">
    <Setter Property="ItemTemplateSelector" Value="{StaticResource MyTemplateSelector}"/>
</Style>

Code Behind

public class TemplateSelector : DataTemplateSelector
{
    DataTemplate stringTemplate; //This would need to be initialized

    //You override this function to select your data template based in the given item
    public override System.Windows.DataTemplate SelectTemplate(object item, 
                    System.Windows.DependencyObject container)
    {
        if(item is string)
            return stringTemplate;
        else
            return base.SelectTemplate(item, container);
    }
}

Obviously, this is an extremely simplified version of what you would end up doing, but it should give you an idea of what is going on.

Since I was using Caliburn.Micro (something I would highly recommend you start using if you aren't already) I was a bit apprehensive of using anything where my back end code would need to grab data templates from my front end (UI). That being said, I wasn't going to be too stubborn, but I am also an inherently lazy person, and I don't like having to do extra work to get things working, something it appeared I would need to do to get a hold of the templates I was trying to supply. The issue is that the DataTemplateSelector doesn't actually have access to the ResourceDictionary used by your user interface, and while it is probably possible to find it through the container parameter, I really didn't feel like doing the work.

So, out comes Google and I start searching for alternative solutions, of which there were surprisingly few. The general response was how to use DataTemplateSelectors and there were a few decent articles on how you could move the selection code out of your apparently numerous selector classes. I wouldn't know, since I was just starting to write my first one, but I took their word for it anyway and kept looking.

After a few minutes I got bored of this and decided to try my hand at writing my own. One of my main aims was to be able to add new template selectors from within my WPF code rather than having to resort to writing code behind. One of the great features of WPF is the ability to register attached properties on objects you do not have the source to. This allows you to add functionality that wasn't there to start with and extend existing classes in creative new ways.

The result was a class which allowed me to add data templates, along with selection criteria, right from within my WPF code using an attached property.

DynamicDataTemplate Implementation

/// <summary>
/// Provides a means to specify DataTemplates to be selected from within WPF code
/// </summary>
public class DynamicTemplateSelector : DataTemplateSelector
{
    /// <summary>
    /// Generic attached property specifying <see cref="Template"/>s
    /// used by the <see cref="DynamicTemplateSelector"/>
    /// </summary>
    /// <remarks>
    /// This attached property will allow you to set the templates you wish to be available whenever
    /// a control's TemplateSelector is set to an instance of <see cref="DynamicTemplateSelector"/>
    /// </remarks>
    public static readonly DependencyProperty TemplatesProperty = 
        DependencyProperty.RegisterAttached("Templates", typeof(TemplateCollection), typeof(DataTemplateSelector),
        new FrameworkPropertyMetadata(new TemplateCollection(), FrameworkPropertyMetadataOptions.Inherits));

                
    /// <summary>
    /// Gets the value of the <paramref name="element"/>'s attached <see cref="TemplatesProperty"/>
    /// </summary>
    /// <param name="element">The <see cref="UIElement"/> who's attached template's property you wish to retrieve</param>
    /// <returns>The templates used by the givem <paramref name="element"/>
    /// when using the <see cref="DynamicTemplateSelector"/></returns>
    public static TemplateCollection GetTemplates(UIElement element)
    {
        return (TemplateCollection)element.GetValue(TemplatesProperty);
    }

    /// <summary>
    /// Sets the value of the <paramref name="element"/>'s attached <see cref="TemplatesProperty"/>
    /// </summary>
    /// <param name="element">The element to set the property on</param>
    /// <param name="collection">The collection of <see cref="Template"/>s to apply to this element</param>
    public static void SetTemplates(UIElement element, TemplateCollection collection)
    {
        element.SetValue(TemplatesProperty, collection);
    }

    /// <summary>
    /// Overriden base method to allow the selection of the correct DataTemplate
    /// </summary>
    /// <param name="item">The item for which the template should be retrieved</param>
    /// <param name="container">The object containing the current item</param>
    /// <returns>The <see cref="DataTemplate"/> to use when rendering the <paramref name="item"/></returns>
    public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {
        //This should ensure that the item we are getting is in fact capable of holding our property
        //before we attempt to retrieve it.
        if (!(container is UIElement))
            return base.SelectTemplate(item, container);

        //First, we gather all the templates associated with the current control through our dependency property
        TemplateCollection templates = GetTemplates(container as UIElement);
        if(templates == null || templates.Count == 0)
            base.SelectTemplate(item, container);

        //Then we go through them checking if any of them match our criteria
        foreach (var template in templates)
            //In this case, we are checking whether the type of the item
            //is the same as the type supported by our DataTemplate
            if (template.Value.IsInstanceOfType(item))
                //And if it is, then we return that DataTemplate
                return template.DataTemplate;
            
        //If all else fails, then we go back to using the default DataTemplate
        return base.SelectTemplate(item, container);
    }
}

/// <summary>
/// Holds a collection of <see cref="Template"/> items
/// for application as a control's DataTemplate.
/// </summary>
public class TemplateCollection : List<Template>
{

}

/// <summary>
/// Provides a link between a value and a <see cref="DataTemplate"/>
/// for the <see cref="DynamicTemplateSelector"/>
/// </summary>
/// <remarks>
/// In this case, our value is a <see cref="System.Type"/> which we are attempting to match
/// to a <see cref="DataTemplate"/>
/// </remarks>
public class Template : DependencyObject
{
    /// <summary>
    /// Provides the value used to match this <see cref="DataTemplate"/> to an item
    /// </summary>
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(Type), typeof(Template));

    /// <summary>
    /// Provides the <see cref="DataTemplate"/> used to render items matching the <see cref="Value"/>
    /// </summary>
    public static readonly DependencyProperty DataTemplateProperty = 
       DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(Template));

    /// <summary>
    /// Gets or Sets the value used to match this <see cref="DataTemplate"/> to an item
    /// </summary>
    public Type Value
    { get { return (Type)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }

    /// <summary>
    /// Gets or Sets the <see cref="DataTemplate"/> used to render items matching the <see cref="Value"/>
    /// </summary>
    public DataTemplate DataTemplate
    { get { return (DataTemplate)GetValue(DataTemplateProperty); } set { SetValue(DataTemplateProperty, value); } }
}

Using the Code

<local:DynamicTemplateSelector x:Key="MyTemplateSelector" />

<DataTemplate x:Key="StringTemplate">
    <TextBlock>
        <Run Text="String: " />
        <Run Text="{Binding}" />
    </TextBlock>
</DataTemplate>

<DataTemplate x:Key="Int32Template">
    <TextBlock>
        <Run Text="Int32: " />
        <Run Text="{Binding}" />
    </TextBlock>
</DataTemplate>

<Style x:Key="MyListStyle" TargetType="ListView">
    <Setter Property="ItemTemplateSelector" Value="{StaticResource MyTemplateSelector}"/>
    <Setter Property="local:DynamicTemplateSelector.Templates">
        <Setter.Value>
            <local:Templates>
                <local:Template Value={x:Type String} DataTemplate={StaticResource StringTemplate}/>
                <local:Template Value={x:Type Int32} DataTemplate={StaticResource Int32Template}/>
            </local:Templates>
        </Setter.Value>
    </Setter>
</Style>

It may not look it just yet, but when you start adding lots of different types, or templates, this quickly becomes a lot easier to manage; allowing you to add templates and types without needing to change any back end code.

Points of Interest

As I said, my main reason for writing this code was to maintain an MVVM structure within the project I was working on. I only recently started writing code to match the MVVM paradigm and I can truly say that it has been one of the best things I've done since I moved away from Java a few years ago. If you haven't yet, take a look at the truly exceptional Caliburn.Micro and put the time into learning how to use it.

Other than that, I'd also recommend learning how to make use of attached Dependency Properties, they are useful for all manner of things from adding watermarks to your text boxes (something I have only seen one person do properly so far in WPF) to allowing Caliburn.Micro to hide and unhide a window using a binding without having to write your own WindowManager.

History

  • 06/07/2012 - Initial release.
  • 09/07/2012 - Fixed some issues with inheritance of DependencyProperty and added a collection to make adding values easier.