Geeklog
There's no place like 127.0.0.1 - ramblings about programming

Silverlight DataTemplateSelector

September 11, 2009 19:03 by The Geek

You may or may not be aware that WPF supports System.Windows.Controls.DataTemplateSelectors.  As the documentation says, this class provides a way to choose a DataTemplate based on the object that it is bound to.  This is really useful behaviour, even though it may not be a daily task.  As developers, we can use a DataTemplateSelector to show a different control for each item in a list - the type of the control is determined by the selector.  For this example, let's assume that we have an abstract class "Animal" with "Snake", "Human" and "Dog" concrete implementations.

    public abstract class Animal
    {
        public string Name { get; set; }

        public abstract string Speak { get; }

        public abstract int Legs { get; }
    }

Next, we can write an AnimalSelector class:

    public sealed class AnimalSelector
        : DataTemplateSelector
    {
        public DataTemplate DogTemplate { get; set; }

        public DataTemplate SnakeTemplate { get; set; }

        public DataTemplate HumanTemplate { get; set; }

        public override DataTemplate SelectTemplate(
            object item,
            DependencyObject container)
        {
            if (item != null && item is Animal)
            {
                if (item is Dog)
                {
                    return this.DogTemplate;
                }
                else if (item is Snake)
                {
                    return this.SnakeTemplate;
                }
                else if (item is Human)
                {
                    return this.HumanTemplate;
                }
            }

            return null;
        }
    }

As you can see, the code is really quite simple.  AnimalSelector inherits DataTemplateSelector and contains properties for each data template that we might use.  SelectTemplate() is invoked when the parent control requires a template and as you can read, based on the type of the object, a template is returned.

"How do we get this in the UI?" you ask - well this is simple if you are familiar with DataTemplates.  If you are not, it doesn't matter because they are really simple - they are just a way to customise the appearance of a control.

Within Window.Resources, I have defined a DataTemplate that will be used to display objects of type Dog:


    <DataTemplate x:Key="dogTemplate">
        <GroupBox Header="{Binding Path=Speak}"
                  Background="AliceBlue">
            <StackPanel Orientation="Horizontal">
                <Label Content="I am a dog and my name is:" />
                <Label Content="{Binding Path=Name}" />
            </StackPanel>
        </GroupBox>
    </DataTemplate>

Templates are also defined for Snakes and Humans.  The next thing that we need is an instance of AnimalSelector:


    <local:AnimalSelector x:Key="selector"
                          DogTemplate="{StaticResource dogTemplate}"
                          HumanTemplate="{StaticResource humanTemplate}"
                          SnakeTemplate="{StaticResource snakeTemplate}" />

The final part of this puzzle is using the selector which we will use to select the template for each item in an ItemsControl:


    <ItemsControl ItemsSource="{StaticResource Animals}"
                  ItemTemplateSelector="{StaticResource selector}" />

After doing all of that, we end up with a pretty screenshot:

As you can see, the different types of object have different controls :-)  Now, back to the purpose of this post - how can we have this behaviour in Silverlight?  Unfortunately, Silverlight does not expose an ItemTemplateSelector property on it's ItemsControl.  The best I have come up with so far is to inherit from ItemsControl and add the things we need to duplicate the functionality:

    public sealed class SelectableItemsControl : ItemsControl
    {
        public static readonly DependencyProperty ItemTemplateSelectorProperty =
            DependencyProperty.Register(
                "ItemTemplateSelector",
                typeof(DataTemplateSelector),
                typeof(SelectableItemsControl),
                null);

        public DataTemplateSelector ItemTemplateSelector
        {
            get
            {
                return (DataTemplateSelector)this.GetValue(
                    ItemTemplateSelectorProperty);
            }

            set
            {
                this.SetValue(ItemTemplateSelectorProperty, value);
            }
        }

        protected override void PrepareContainerForItemOverride(
            DependencyObject element,
            object item)
        {
            base.PrepareContainerForItemOverride(element, item);

            DataTemplateSelector selector = this.ItemTemplateSelector;
            if (selector != null)
            {
                ((ContentPresenter)element).ContentTemplate =
                    selector.SelectTemplate(item, element);
            }
        }
    }

As you can see, we declare a DependencyProperty, expose it and override PrepareContainerForItemOverride.  Simple when you know how ;-)  You will have noticed that our new property is of type DataTemplateSelector.  Where did this come from? I wrote it :p

    public class DataTemplateSelector
    {
        public DataTemplateSelector()
        {
        }

        public virtual DataTemplate SelectTemplate(
            object item,
            DependencyObject container)
        {
            return null;
        }
    }

We can now use nearly the same XAML in Silverlight as in WPF with the obvious expection of the control name:


    <local:SelectableItemsControl
        ItemsSource="{StaticResource Animals}"
        ItemTemplateSelector="{StaticResource selector}"  />

At long last we get a pretty screenshot from Silverlight:

It's not quite the same but I think it is good enough to illustrate the principle.  The problem with this approach is that if you need similar functionality on one of the controls that inherits from ItemsControl, such as ListBox or TabControl, you would need to override their behaviour as well.

You can download a sample project here: TemplateSelector.zip (1.41 mb)


Comments

September 11. 2009 22:27

pingback

Pingback from wpfdc.com.cn

Silverlight DataTemplateSelector -  WPFdc

wpfdc.com.cn

May 7. 2010 05:43

pingback

Pingback from condosencinitas.com

car insurance cheap quotes

condosencinitas.com