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.

ocodo
  • 29,401
  • 18
  • 105
  • 117

2 Answers2

37

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;
H.B.
  • 166,899
  • 29
  • 327
  • 400
  • 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? – ocodo Apr 13 '11 at 04:50
  • What do you mean? What is "unclean"? – H.B. Apr 13 '11 at 04:54
  • 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) – ocodo Apr 13 '11 at 04:59
  • 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. – H.B. Apr 13 '11 at 05:02
  • Code copied as as and with modification does not display anything at all (only empty ListView). Is there something I'm missing? – javajavajavajavajava May 01 '13 at 19:07
  • @javajavajavajavajava: Are you familiar with [data binding](http://msdn.microsoft.com/en-us/library/ms752347.aspx)? Because you can't just copy my code and expect it to work. (see also: [`DataContext`](http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.datacontext.aspx)) – H.B. May 02 '13 at 00:34
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}"
             >
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • 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. – ocodo Apr 13 '11 at 04:51
  • 5
    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. – H.B. Apr 13 '11 at 04:53
  • 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. – ocodo Apr 13 '11 at 04:56
  • 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. – H.B. Apr 13 '11 at 05:09