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.
Now check out the UI, you’ll see the fields added at each inheritance level correspond to a Blue expander.
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.