More. DataTemplates. In. WPF.

18 Feb 2011 in dotnet | libs-and-frameworks |

Imagine you want to fill a toolbar through its ItemsSource and appropriate DataTemplates and not all commands are created equal. Some commands should be shown as Buttons, or ToggleButtons or whatever Visual tree you may come up with. In such a situation you can use a DataTemplateSelector. How to implement one (not exceedingly complicated) is described here. Alas, it requires you to leave the realms of XAML. One idea is then to have a XAML-friendly template selector. The idea isn’t new, one implementation can be found here. However I wanted something even simpler: A XAML-based template selector that is only driven by the DataType specified on the template. The API shown makes it possible to have the XAML look like this:

     <ToolBar ItemsSource="{Binding Path=Control.Commands}">
        <ToolBar.ItemTemplateSelector>
            <local:DataTemplateChoice>
                <DataTemplate DataType="{x:Type cmd:CommandA}">
                    <Button Height="30" Width="30" Command="{Binding Command}">
                        <Image Source="{Binding SymbolResourceName, 
                            Converter={StaticResource imgConv}}" />
                    </Button>
                </DataTemplate>
                <DataTemplate DataType="{x:Type cmd:CommandB}">
                    <ToggleButton Height="30" Width="30" 
                                    Command="{Binding Command}" 
                                    IsChecked="{Binding IsActive, Mode=OneWayToSource}">
                        <Image Source="{Binding SymbolResourceName, 
                            Converter={StaticResource imgConv}}" />
                    </ToggleButton>
                </DataTemplate>
                <StaticResourceExtension ResourceKey="BasicCommandInfoDisplay" />
            </local:DataTemplateChoice>
        </ToolBar.ItemTemplateSelector>
    </ToolBar>

The DataTemplateChoice is a container for an unlimited number of DataTemplates. The implementation is rather simple:

    [ContentProperty("Templates")]
    public class DataTemplateChoice : DataTemplateSelector
    {
        public DataTemplateChoice()
        {
            Templates = new DataTemplates();
        }

        public DataTemplates Templates { get; private set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var dt = Templates.GetMatchFor(item.GetType());
            return dt ?? base.SelectTemplate(item, container);
        }
    }

Rather important for comfort is the specification of the ContentPropertyAttribute. That way the XAML compiler knows which property to target per default, thereby allowing to write straight into the DataTemplateChoice and ensuring that the content ends up in the Templates property.

Finally we need the DataTemplates class, which is pretty much just a list with a method to pick out a DataTemplate:

    public class DataTemplates : List<DataTemplate>
    {
        internal DataTemplate GetMatchFor(Type objectType)
        {
            var dataTemplate = this.FirstOrDefault(t => MatchViaDataType(t, objectType));
            return dataTemplate;
        }

        private static bool MatchViaDataType(DataTemplate arg, Type objectType)
        {
            var type = arg.DataType as Type;
            return type != null &amp;&amp; type.IsAssignableFrom(objectType);
        }
    }

And that’s that! Make sure to specify the DataType on the DataTemplates you add. Via the StaticResourceExtension you can add DataTemplates specified elsewhere. The selection is similar to the catch blocks of a try: Move down from specific to more general. And finally, specifying an interface as DataType will work without problems due to usage of IsAssignableFrom.

Shout it

Chronology

  |  
comments powered by Disqus