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.