Stacking DataTemplates – Code Reuse in WPF

Today I was working on the UI for our Product business objects which use a bit of inheritance, it turned into a nice concise example of a DataTemplate stacking technique that I use.

First have a quick look at the class diagram, you’ll see some basic inheritance and a few fields added at each level Product – Ingredient – Drug.

ProductsDiag Now check out the UI, you’ll see the fields added at each inheritance level correspond to a Blue expander.

ProductUI To achieve this effect I use a series of ContentControls, DataTemplates and a TemplateSelector to handle the splitting of the object types.

The main DataTemlpate for all Products looks like this:

<DataTemplate x:Key="ItemReadOnlyTemplate">
    <StackPanel>
        <Expander Header="Product Detail" IsExpanded="{Binding Path=ROExpanderStates[0], Source={StaticResource ViewModelODP}}">
            <Grid >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.3*" />
                    <ColumnDefinition Width="0.7*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <TextBlock Grid.Column="0" Grid.Row="0" Text="Code:" Style="{StaticResource LabelTBStyle}"/>
                <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Path=Code}" Style="{StaticResource ValueTBStyle}"/>

                <Border Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Style="{StaticResource AlternateGridTextBorderStyle}" />
                <TextBlock Grid.Column="0" Grid.Row="1" Text="Name:" Style="{StaticResource LabelTBStyle}"/>
                <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Path=Name}"  Style="{StaticResource ValueTBStyle}"/>

                <TextBlock Grid.Column="0" Grid.Row="2" Text="Type:" Style="{StaticResource LabelTBStyle}"/>
                <TextBlock Grid.Column="1" Grid.Row="2" Text="{Binding Path=Type.Name}" Style="{StaticResource ValueTBStyle}"/>
            </Grid>
        </Expander>
        <ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ROTempateSelector}" />
    </StackPanel>
</DataTemplate>

Expander displays all the fields from the base “Product” object, the ContentControl at the bottom is hooked up to a TemplateSelector to feed in any additional DataTemplates based on the object type.

The TemplateSelector definition look like this:

<Qaf_Local:ProductTemplateSelector x:Key="ROTempateSelector"
    DefaultTemplate="{StaticResource ItemRODefaultTemplate}"
    IngredientTemplate="{StaticResource ItemROIngredientTemplate}"
    DrugTemplate="{StaticResource ItemRODrugTemplate}"
    FinishedProductTemplate="{StaticResource ItemROFinishedProductTemplate}"
                                   />

Fairly self explanatory, the only DataTemlpate that does anything tricky is the ItemRODrugTemplate which uses a similar technique as above to stack the ItemROIngredientTemplate in it’s own DataTemplate, which looks like:

<DataTemplate x:Key="ItemRODrugTemplate">
    <StackPanel>
        <ContentControl Content="{Binding}" ContentTemplate="{StaticResource ItemROIngredientTemplate}" />
        <Expander Header="Drug Details" IsExpanded="{Binding Path=ROExpanderStates[2], Source={StaticResource ViewModelODP}}">
            <Grid >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.3*" />
                    <ColumnDefinition Width="0.7*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                </Grid.RowDefinitions>
                <TextBlock Grid.Column="0" Grid.Row="0" Text="Is S4:" Style="{StaticResource LabelTBStyle}"/>
                <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Path=IsS4}" Style="{StaticResource ValueTBStyle}"/>
            </Grid>
        </Expander>
    </StackPanel>
</DataTemplate>

The one trick you need to look out for when stacking the DataTemplates like this is the weird binding with no path:

<ContentControl Content="{Binding}" ContentTemplate="{StaticResource ItemROIngredientTemplate}" />

This passes the current DataContext (the Product object) through to the ContentControl, without it you’ll get blank fields.

Leave a Reply

Your email address will not be published.