DataTemplate for each DataType in a GridViewColumn CellTemplate
Asked Answered
H

1

18

I have an ObservableCollection which contains view models of multiple types, and I would like to make a DataTemplate for each type within each of my GridViewColumn's CellTemplates. In this simple example I could create a base ViewModel, but I would like to be able to do this just from xaml. The xaml below shows what I am trying to do, where one of the DataTemplates would be used for each CellTemplate.

If there were a GridViewColumn.Resources I would define the DataTemplates there then use a a DataTemplate with ContentPresenter in the CellTemplate, but I obviously can't do that. I'm thinking I may need a TemplateSelector, but I'm not sure where to start.

<ListView ItemsSource={Binding GenericObservableCollection>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Type">
                <GridViewColumn.CellTemplate>
                    <DataTemplate DataType="{x:Type vm:ActionInputViewModel}">
                        <TextBlock Text="Input"/>
                    </DataTemplate>
                    <DataTemplate DataType="{x:Type vm:ActionOutputViewModel}">
                        <TextBlock Text="Output"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Value">
                <GridViewColumn.CellTemplate>
                    <DataTemplate DataType="{x:Type vm:ActionInputViewModel}">
                        <TextBlock Text="{Binding Property1}"/>
                    </DataTemplate>
                    <DataTemplate DataType="{x:Type vm:ActionOutputViewModel}">
                        <TextBlock Text="{Binding Property2}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>
Hydranth answered 1/2, 2013 at 18:14 Comment(0)
C
38

There are a few different ways you could go here. You could write a DataTemplateSelector and assign that to the GridViewColumn.CellTemplateSelector property:

public class ViewModelTemplateSelector : DataTemplateSelector
{
    public DataTemplate InputTemplate { get; set; }
    public DataTemplate OutputTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return (item is ActionInputViewModel) ? InputTemplate : OutputTemplate;
    }
}

Then you can move all the templates to Resources somewhere - here I've just stuck it in the ListView for brevity:

    <ListView ItemsSource="{Binding GenericObservableCollection}">
        <ListView.Resources>
            <DataTemplate x:Key="InLabel" DataType="{x:Type vm:ActionInputViewModel}">
                <TextBlock Text="Input"/>
            </DataTemplate>
            <DataTemplate x:Key="OutLabel" DataType="{x:Type vm:ActionOutputViewModel}">
                <TextBlock Text="Output"/>
            </DataTemplate>
            <DataTemplate x:Key="InValue" DataType="{x:Type vm:ActionInputViewModel}">
                <TextBlock Text="{Binding Property1}"/>
            </DataTemplate>
            <DataTemplate x:Key="OutValue" DataType="{x:Type vm:ActionOutputViewModel}">
                <TextBlock Text="{Binding Property2}"/>
            </DataTemplate>
        </ListView.Resources>
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Type">
                    <GridViewColumn.CellTemplateSelector>
                        <vm:ViewModelTemplateSelector InputTemplate="{StaticResource InLabel}" OutputTemplate="{StaticResource OutLabel}"/>
                    </GridViewColumn.CellTemplateSelector>
                </GridViewColumn>
                <GridViewColumn Header="Value">
                    <GridViewColumn.CellTemplateSelector>
                        <vm:ViewModelTemplateSelector InputTemplate="{StaticResource InValue}" OutputTemplate="{StaticResource OutValue}"/>
                    </GridViewColumn.CellTemplateSelector>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>

Alternatively, if you want to keep it all in XAML you can rely on the DataTypes to resolve the right templates for you. Normally you would just put them into the Resources collection of the closest container but unfortunately GridViewColumn isn't a UI element so doesn't have a Resources collection. You can get around this by adding in a ContentControl for each cell which can hold its own typed templates:

    <ListView ItemsSource="{Binding GenericObservableCollection}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Type">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <ContentControl Content="{Binding}">
                                <ContentControl.Resources>
                                    <DataTemplate DataType="{x:Type vm:ActionInputViewModel}">
                                        <TextBlock Text="Input"/>
                                    </DataTemplate>
                                    <DataTemplate DataType="{x:Type vm:ActionOutputViewModel}">
                                        <TextBlock Text="Output"/>
                                    </DataTemplate>
                                </ContentControl.Resources>
                            </ContentControl>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="Value">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <ContentControl Content="{Binding}">
                                <ContentControl.Resources>
                                    <DataTemplate DataType="{x:Type vm:ActionInputViewModel}">
                                        <TextBlock Text="{Binding Property1}"/>
                                    </DataTemplate>
                                    <DataTemplate DataType="{x:Type vm:ActionOutputViewModel}">
                                        <TextBlock Text="{Binding Property2}"/>
                                    </DataTemplate>
                                </ContentControl.Resources>
                            </ContentControl>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>

Either way should give you the same result.

Cracksman answered 1/2, 2013 at 19:38 Comment(4)
Perfect. Thanks for providing detailed answers for both of the approaches that I had considered.Hydranth
Geez, I was sooo close after flailing for hours, but you nailed it. My error was that I was binding the DataContext of the ContentControl, as opposed to the Content as you correctly point out. Kudos and thanks.Broad
I love it when people actually spend time on their answer. It is evident in the detail you provided. Nice job. +1.Jerrine
Important to note: In the first approach using the CellTemplateSelector do not set the property DisplayMemberBinding as you might do for other columns in the ListViewSchleswig

© 2022 - 2024 — McMap. All rights reserved.