Conditional List itemtemplate or datatemplate in WPF
Asked Answered
A

2

22

This may be an obvious question, but I think there may well be multiple ways to implement it, so not only will this be useful to me, hopefully it will be useful to others.

Essentially I'm looking for the best way to implement a list view that can accept different types of objects and then renders them with the appropriate item/data template for that object.

So for example... we have a standard product list view, and when we view different categories the business has decided it would like to show a different item template style for each different category.

The main reason for asking this here, is to avoid a nasty hacky solution and discover a good clean method instead.

Hopefully I've provided enough information, let me know if you need more.

Andi answered 13/4, 2011 at 4:25 Comment(0)
L
38

Just specifying DataTemplates in the Resources with the respective DataType is enough, e.g.

<ListView ItemsSource="{Binding Data}">
    <ListView.Resources>
        <!-- Do NOT set the x:Key -->
        <DataTemplate DataType="{x:Type local:Employee}">
            <TextBlock Text="{Binding Name}" Foreground="Blue"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Machine}">
            <TextBlock Text="{Binding Model}" Foreground="Red"/>
        </DataTemplate>
    </ListView.Resources>
</ListView>

Screenshot

(Note that DataTemplate.DataType can also be used for implicit XML data templating (see docs), the property type for that reason is not System.Type, so unlike in Style.TargetType you have to use x:Type to reference a CLR-type. If you just enter a string it will not be converted to a type.)

You might also want to look into CompositeCollections, to get clean merged lists of varying types.


Sample data i used:

ObservableCollection<Employee> data = new ObservableCollection<Employee>(new Employee[]
{
    new Employee("Hans", "Programmer")      ,
    new Employee("Elister", "Programmer")   ,
    new Employee("Steve", "GUI Designer")   ,
    new Employee("Stephen", "GUI Designer") ,
    new Employee("Joe", "Coffee Getter")    ,
    new Employee("Julien", "Programmer")    ,
    new Employee("John", "Coffee Getter")   ,
});
ObservableCollection<Machine> data2 = new ObservableCollection<Machine>(new Machine[]
{
    new Machine("XI2",    String.Empty),
    new Machine("MK2-xx", String.Empty),
    new Machine("A2-B16", String.Empty),
});
CompositeCollection cc1 = new CompositeCollection();
cc1.Add(new CollectionContainer() { Collection = data });
cc1.Add(new CollectionContainer() { Collection = data2 });
Data = cc1;
Laureate answered 13/4, 2011 at 4:28 Comment(8)
Thanks, that's great. If you had a really large set of different items, can you think of a way that would make this cleaner?Andi
What do you mean? What is "unclean"?Laureate
If you have a very large number of items, you will have a very large list of DataTemplate nodes in your template, I was wondering if there was a strategy to manage this maintenance issue. (Don't worry too much, this is a hypothetical for my case, I was asking for later reference)Andi
You could possibly split and refactor it into various ResourceDictionaries and merge them at some point, but i have not encountered such issues before so i cannot give you any clear advice on that.Laureate
Code copied as as and with modification does not display anything at all (only empty ListView). Is there something I'm missing?Rappel
@javajavajavajavajava: Are you familiar with data binding? Because you can't just copy my code and expect it to work. (see also: DataContext)Laureate
Great, but one more question: How can, in a DataGrid application, this: <DataGridTemplateColumn CellTemplate="{StaticResource MyTemplate}" refer to TWO instances of DataTemplate for each data type ? Probably really simple, but this is a complete mystery to meNihil
@Roland: Is the cell template still necessary if you have implicit data templates? If so, just put <ContentPresenter Content="{Binding}"/> into it and it should apply templates defined by type in some parent's resources.Laureate
P
13

One option is to create a DataTemplateSelector in your code:

public class QueueDisplayDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {

        var listBoxItem = item as JobQueueListBoxItem;
        var resourceName = String.Empty;
        switch (listBoxItem.JobQueueListBoxItemType)
        {
            case JobQueueListBoxItemType.QueuedJob :
                resourceName = "DataTemplateQueuedJob";
                break;
            case JobQueueListBoxItemType.TransferWorker :
                resourceName = "DataTemplateTransferWorker";
                break;
            default:
                throw new InvalidOperationException(string.Format("There is no corresponding list box template for {0}", listBoxItem.JobQueueListBoxItemType));
        }
        var element = container as FrameworkElement;
        return element.FindResource(resourceName) as DataTemplate;
    }
}

This would then be declared in your XAML as a resource

        <ResourceDictionary>
            <local:QueueDisplayDataTemplateSelector x:Key="QueueDisplayDataTemplateSelector" />

And then you would assign this to you list box:

    <ListBox ItemsSource="{Binding ListBoxContents}" 
             ItemTemplateSelector="{StaticResource QueueDisplayDataTemplateSelector}"
             >
Perplex answered 13/4, 2011 at 4:48 Comment(4)
Yes, this is probably how I would've hacked it, I think the x:type is cleaner, both suffer a bit with a large set of alternative items.Andi
This is not necessarily a hack, DataTemplateSelector is a "proper" property for a reason, if you only differentiate based on type it's redundant but using selectors you can apply different templates to objects of the same type for example.Laureate
To justify the use of the word "hack", I'm saying it due to the use of a case instead of using polymorphism to manage the template selection.Andi
I doubt that the internal mechanism of WPF does anything other than looking at the type and getting the respective DataTemplate from the dictionary, after all there is no inherited method attached to the data-objects which could provide it. It's not that different, but it has less room for error i guess.Laureate

© 2022 - 2024 — McMap. All rights reserved.