Search code examples
wpfxamlinheritancelistboxdatatemplate

How to add new elements to the base Grid from an inherited DataTemplate?


I have two objects: Bicycle and BicycleFullSuspension, which inherits Bicycle and contains additional properties (strings and decimals). I managed to make a functioning DataTemplateSelector that will choose either my LbxItemTemplateBicycle DataTemplate or my LbxItemTemplateFullSuspension DataTemplate, depending on to which class the object in an ObservableCollection belongs. Both of these templates are bound to the parent template ListBoxItemTemplate.

I am trying to display information about each object in a ListBox, and each of the inheriting templates should add a few more fields to the grid in the parent DataTemplate. My problem is that I cannot figure out how to add WPF elements to the grid in ListBoxItemTemplate from one of the inherited templates. I cannot assign a key to the grid in the template, so I am not sure how to specify that additional TextBlocks should end up in the same grid that is in the parent template. (Right now, additional TextBlocks are stacked on top of the parent grid.)

<DataTemplate x:Key="ListBoxItemTemplate">
        <Border Name="LbxItemTemplate" BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5">
        <Grid>
            <Grid.RowDefinitions>
                ...
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                ...
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="1" Grid.Column="0" Text="Bike Year:" />
            <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BikeYear}" />
            <TextBlock Grid.Row="2" Grid.Column="0" Text="Bike Color: "/>
            <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding BikeColor}"/>
            ...
            </Grid>
        </Border>
    </DataTemplate>
   <DataTemplate x:Key="LbxItemTemplateFullSuspension" DataType="{x:Type app:BicycleFullSuspension}">
        <Grid>
        <ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
            <TextBlock Grid.Row="6" Grid.Column="0" Text="Shock Travel"/>
            <TextBlock Grid.Row="6" Grid.Column="1" Text="{Binding ShockTravel}"/>
        </Grid>
    </DataTemplate>

I found these links helpful for getting to this point:

http://dariosantarelli.wordpress.com/2011/07/28/wpf-inheritance-and-datatemplates/ http://zamjad.wordpress.com/2009/12/31/applying-data-template-conditionally/

Is there a way to use data-template inheritance in WPF?

Edit:

I'm not sure why I didn't think to put the Border on the template inheriting the base, but by nesting a StackPanel inside of the Border in the inheriting template (StackPanel contains the ContentPresenter with the base template content along with the Grid that has the added information), everything lines up very nicely:

Working solution, using input from XAMeLi:

<DataTemplate x:Key="ListBoxItemTemplate">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                ...
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150" SharedSizeGroup="LabelColumnGroup" />
                <ColumnDefinition Width="150" SharedSizeGroup="LabelColumnGroup" />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="1" Grid.Column="0" Text="Bike Year:" />
            <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BikeYear}" />
            ...
        </Grid>
   </DataTemplate>
   <DataTemplate x:Key="LbxItemTemplateFullSuspension" DataType="{x:Type app:BicycleFullSuspension}" >
        <Border BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5" Grid.IsSharedSizeScope="True" Width="500">
            <StackPanel>
            <ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    ...
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumnGroup" />
                    <ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumnGroup" />
                </Grid.ColumnDefinitions>
                ...
                <TextBlock Grid.Row="7" Grid.Column="0" Text="Shock Type: "/>
                <TextBlock Grid.Row="7" Grid.Column="1" Text="{Binding ShockType}"/>
                ...
            </Grid>
            </StackPanel>
        </Border>
    </DataTemplate>

Solution

  • You cannot add UIElemets to a data template, but you can layout the bigger template to look like it has added text to the base template.

    Basically, you are on the right path, only the layout of the big template is corrupt. (Too bad you omitted the structure of your rows and columns)

    Try this:

    <DataTemplate x:Key="ListBoxItemTemplate">
        <Border Name="LbxItemTemplate" BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5">
        <Grid>
            <Grid.RowDefinitions>
                ...
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"
                                  SharedSizeGroup="LabelGroupColumn"/>
                ...
            </Grid.ColumnDefinitions>
          ....
        </Border>
    </DataTemplate>
    
    <DataTemplate x:Key="LbxItemTemplateFullSuspension">
        <Grid Grid.IsSharedSizeScope="True">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/><!--This will hold the default template-->
                <!--From here define the same structure as in the default template, if you have rows for margins and such-->
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"
                                  SharedSizeGroup="LabelGroupColumn"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
            <TextBlock Grid.Row="1" Grid.Column="0" Text="Shock Travel"/>
            <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ShockTravel}"/>
        </Grid>
    </DataTemplate>
    

    It seems that your grids have Label-Value structures and you want all the values to start from the same vertical line (this is very good from UX point of view). Notice the SharedSizeGroup setters on column definitions and the Grid.IsSharedSizeScope="True" on a mutual parent. This will keep the width of the label column to be synchronized among the grids.