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

2

54

Is it possible to have DataTemplate composition or inheritance (similar to "BasedOn" in Styles)? There are 2 instances where I need that.

  1. For inherited classes: I have a base class with several inherited classes. I don't want to duplicate the base class template in each of the derived class's DataTemplate.

  2. Different Views: for the same class I want to define a datatemplate, and then add to that template as appropriate. Ex. the base template will display the data in the object and then i want different templates that can perform different actions on the object, while displaying the data (inheriting the base template).

Meltage answered 14/12, 2010 at 20:0 Comment(0)
B
53

The only thing that I have found do to for this kind of thing is this:

<DataTemplate x:Key="BaseClass">
  <!-- base class template here -->
</DataTemplate>
<DataTemplate DataType="{x:Type app:BaseClass}">
  <ContentPresenter Content="{Binding}" 
                    ContentTemplate="{StaticResource BaseClass}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type app:DerivedClass}">
  <StackPanel>
    <ContentPresenter Content="{Binding}" 
                      ContentTemplate="{StaticResource BaseClass}"/>
    <!-- derived class extra template here -->
  </StackPanel>
</DataTemplate>

Basically this creates a "common" template that can be referenced using a key (BaseClass in this case). Then we define the real DataTemplate for the base class, and any derived classes. The derived class template would then add it's own "stuff".

There was some discussion about this on msdn a while back, but no one came up with a better solution that I saw.

Brader answered 14/12, 2010 at 20:15 Comment(2)
+1 because your code worked also on windows phone,while the other did'ntAgatha
I'm confused. This solution appears to be stacking content ontop of each other instead of reducing code with minor tweaks (i.e. inheritance).Archaize
M
25

@Fragilerus and @Liz, actually I think I did come up with something better. Here's another approach that not only avoids the extra ContentPresenter binding, but also removes the need to have to apply a template within a template since the shared content is direct content which is set at compile-time. The only thing that happens at run-time would be the bindings you set inside the direct content. As such, this greatly speeds up the UI when compared to the other solution.

<!-- Content for the template (note: not a template itself) -->
<Border x:Shared="False" 
        x:Key="Foo" 
        BorderBrush="Red" 
        BorderThickness="1" 
        CornerRadius="4">
    <TextBlock Text="{Binding SomeProp}" />
</Border>

<DataTemplate x:Key="TemplateA">
    <!-- Static resource - No binding needed -->
    <ContentPresenter Content="{StaticResource Foo}" /> 
</DataTemplate>

<DataTemplate x:Key="TemplateB">
    <!-- Static resource - No binding needed -->
    <ContentPresenter Content="{StaticResource Foo}" />
</DataTemplate>

Important: Make sure to use the x:Shared attribute on your shared content or this will not work.

The WPF'y Way

The above said, this really isn't the most WPF-friendly way to do what you're after. That can be achieved using the DataTemplateSelector class which does exactly that... select's a data template based on whatever criteria you set.

For instance, you could easily set one up that looks for your known data types and returns the same DataTemplate for both of them, but for all other types, it falls back to the system to resolve the DataTemplate. That's what we actually do here.

Hope this helps! :)

Masonry answered 7/5, 2011 at 8:46 Comment(8)
I'm confused. This solution appears to be stacking content ontop of each other instead of reducing code with minor tweaks (i.e. inheritance).Archaize
What if I wanted to change the ForegroundBrush on the TextBlock to Red on TemplateA but not on TemplateB?Luik
You can apply a local style to the DataTemplate that will do exactly that. Remember, at runtime everything is essentially just expanded out anyway and regular style/property inheritance rules apply.Masonry
x:Shared: "When set to false, modifies WPF resource-retrieval behavior so that requests for the attributed resource create a new instance for each request instead of sharing the same instance for all requests." msdn.microsoft.com/en-us/library/aa970778(v=vs.110).aspxPanaggio
Exactly. That's what you want here. Remember, the resource is actual content, not a template so without setting shared to false, you would be trying to insert the same control instance in multiple places in the UI which is of course physically impossible. This is the same end result as using a template as while the template is shared, it's generated output is not. Those too are discretely-generated instances. This just bypasses using a template and sets the contents directly.Masonry
Get another upvote, nice and elegant solution that significantly reduces UI duplication.Monofilament
An actual DataTemplate (or ControlTemplate) could in principle be used instead of a Control defined as a Resource, if that was needed. The remaining principle (instantiating it inside subclass-specific templates) would be done the same way this answer proposes.Furbish
To clarify [re Scott Nimrod's confusion in first comment]: In question's case #1, which adds additional content to each derived template, still need to wrap in a container such as StackPanel, as shown in Liz's answer. This answer shows that the <ContentPresenter .../> part of Liz's answer can be simplified, to use the common content directly, rather than binding to another DataTemplate.Idolla

© 2022 - 2024 — McMap. All rights reserved.