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

Is it possible to have DataTemplate composition or inheritance (similar to "BasedOn" in Styles)? There are 2 instances where I need that.
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.
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).

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.

#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! :)

Related

How to change from binding ObervableCollection to a TabControl to a ContentControl

I'm very new to WPF and am writing an application using this example as a starting point
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090025
I will only have one workspace visible at any one time, so I want to get rid of the TabControl and use something simple instead - probably a ContentControl, I'm really not sure but all it needs to do is have content in and be closable. So I am trying to replace this:
<DataTemplate x:Key="WorkspacesTemplate"><TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
with:
<DataTemplate x:Key="WorkspacesTemplate">
<ContentControl Content="{Binding ??}" ContentTemplate="{StaticResource ClosableTabItemTemplate}"/>
</DataTemplate>
but I don't know what to bind to. The code in the example seems to use CollectionViewSource to set the active workspace - it's the active workspace that I am interested in but I don't understand what TabControl is doing except that it's something to do with IsSynchronizedWithCurrentItem="True"
The template is invoked from here (Workspaces is the ObservableCollection of ViewModels):
<HeaderedContentControl Content="{Binding Path=Workspaces}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="Workspaces" Style="{StaticResource MainHCCStyle}"/>
and here is the ClosableItem template:
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16"
/>
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center"
/>
</DockPanel>
</DataTemplate>
Please can someone explain what I need to do? Thanks
The WorkspacesTemplate is telling WPF how to display the Workspaces property, which, as you say is an ObservableCollection of ViewModels.
So, the WorkspacesTemplate says, display all these ViewModels in a tab control, and for each ViewModel, use the ClosableTabItemTemplate to display the ViewModel in a tab.
Since you only want one workspace visible at a time, you don't need to expose a collection of workspaces from your ViewModel, and you don't need a tab control to display them. You simply expose the one current workspace from your ViewModel and provide some XAML to display it.
If you still want to use a template to wrap the ViewModel, then yes, you can just use a ContentControl to invoke the template:
<DataTemplate x:Key="MySingleWorkspaceTemplate">
<TextBlock Text={Binding Blah} />
<!-- etc -->
</DataTemplate>
and to invoke the template
<ContentControl Content="{Binding CurrentWorkspace}" ContentTemplate="{StaticResource MySingleWorkspaceTemplate}"/>
However, if this is the only place that the XAML is going to be used, you might as well forget the template and just declare the XAML directly. Eg, (instead of ContentControl)
<TextBlock Text={Binding CurrentWorkspace.Blah} />
<!-- etc -->
EDITED TO ADD:
I think you might be getting confused because currently the ViewModel has no concept of the "Selected Workspace", it just exposes a collection. For completeness (but don't worry about all this), the selection is introduced by the TabControl which indirectly uses the default CollectionView for the Workspaces collection, and CollectionView has the concept of a selected item. This is all in the view.
I wouldn't worry about any of this now, just expose the one workspace yourself from your ViewModel.
EDIT2:
Your close button is appearing because you are explicitly setting a ContentTemplate on your HeaderedContentControl. This template will appear regardless of Content.
To make a template only appear when there is data in Content, make the template implicit instead. If you add a DataType to your template definition (and remove the key), you tell WPF to always use this template to display an object of that data type.
<DataTemplate DataType="{x:Type vm:WorkspaceViewModel}">
<!-- Blah -->
</DataTemplate>
Then you can remove the explicit template from your HeaderedContentControl. Simply setting the Content will be enough to invoke the template, and if there is no Content, there is no template.
<HeaderedContentControl Content="{Binding Path=CurrentWorkspace}" />
(ps. If you're not using the header of HeaderedContentControl, you might as well just use a bog standard ContentControl)

How can you enable auto-DataTemplate selection based on data type like you can with an items control?

We're writing a very specialized ItemsControl which actually has three ContentPresenter's per 'row', each bound to a different object (think poor-man's grid) instead of the more common one, like a ListBox.
Now with a ListBox if you don't explicitly specify either an ItemTemplate or an ItemTemplateSelector, there seems to be some internal selector that applies the template based purely on data type. However, our ContentPresenter's aren't picking them up. We've also tried switching them to ContentControl's instead, but that hasn't worked either.
Now I know I can simply write my own DataTypeTemplateSelector that does this, but I'm wondering if that functionality is already 'baked in' somewhere considered its used with so many ItemsControl's (ListBox, TreeView, ComboBox', DataGrid, etc.) and according to this MSDN article...
http://msdn.microsoft.com/en-us/library/ms742521.aspx
...it should work by default! But again, it doesn't.
Here's our (pseudo) code...
<UserControl.Resources>
<!-- These all work when the relevant items are in a ListBox,
but not with stand-alone ContentPresenters or ContentControls -->
<DataTemplate DataType="local:SomeTypeA">
<TextBlock Text="{Binding Converter={c:DataTypeNameConverter}}" Foreground="Blue" />
</DataTemplate>
<DataTemplate DataType="local::SomeTypeB">
<TextBlock Text="{Binding Converter={c:DataTypeNameConverter}}" Foreground="Purple" />
</DataTemplate>
<DataTemplate DataType="local::SomeTypeC">
<TextBlock Text="{Binding Converter={c:DataTypeNameConverter}}" Foreground="Purple" />
</DataTemplate>
</UserControl.Resources>
<!-- These don't pick up the templates -->
<ContentControl Content="{Binding Field1}" />
<ContentPresenter Content="{Binding Field2}" />
<!-- This however does -->
<ListBox ItemsSource="{Binding AllItems}"
So... anyone want to take a stab at why not?
DataType, for whatever crazy reason, is of type Object, the DataTemplates hence have a string set in that property unless you use x:Type.
Edit: There is a very good reason for the property being an object, as always those who can (and do) read are clearly at an advantage:
If the template is intended for object data, this property contains the type name of the data object (as a string). To refer to the type name of the class, use the x:Type Markup Extension. If the template is intended for XML data, this property contains the XML element name. See the documentation remarks for details about specifying a non-default namespace for the XML element.

Is there something in WPF similar to Style.BasedOn for DataTemplate?

At the moment, I have two very large DataTemplate objects to display two sets of items in two ListBoxes. The DataTemplates are referenced in the ContentTemplate property in two Styles that are set in the ItemContainerStyle properties of the two ListBoxes. The items are of the same type and the DataTemplates are identical except for the following control:
From DataTemplate1
<TextBlock Style="{StaticResource TextStyle}" FontSize="20" Foreground="White"
HorizontalAlignment="Left" Panel.ZIndex="2" Text="{Binding RemainingTime.TotalHours,
Converter={StaticResource DoubleToIntegerConverter}, StringFormat={}{0:#00}}" />
From DataTemplate2
<TextBlock Style="{StaticResource TextStyle}" FontSize="20" Foreground="White"
HorizontalAlignment="Left" Panel.ZIndex="2" Text="{Binding ElapsedTime.TotalHours,
Converter={StaticResource DoubleToIntegerConverter}, StringFormat={}{0:#00}}" />
Is there some way to avoid duplicating the whole Dataemplate but still have this one difference in the text binding of this TextBlock in the second template?
No, there is no inheritance for DataTemplate. If you think about, how would you override a part of a DataTemplate?
Solution: Use another Style to capture the common properties between the two templates. You can scope it in the same Resources block if it only place you need it. It is much cleaner or more WPF way of doing things.
I've already asked this question here once and unfortunately there isn't.
but in this specific situation you can move the fontsize,foreground,horizontalalignment..etc to a style (lets say textstyle2) that based on your current textstyle.
I got an answer to this from another post (by Liz). Basically, you can put all common controls into one DataTemplate and then create two more DataTemplates that each use the first one as a ContentTemplate in a ContentPresenter. Then, you can add different controls into one or both of the latter DataTemplates. Liz provided a code example.
<DataTemplate x:Key="UserTemplate">
<!-- show all the properties of the user class here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:User}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Author}">
<StackPanel>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
<!-- show all the additional Author properties here -->
</StackPanel>
</DataTemplate>
Thanks once again Liz.
Adding to what Dennis suggested, you can always create a custom control that you just stick inside your DataTemplate and re-style that control instead of the DataTemplate.

UserControl as Content for HeaderedContentControl.HeaderTemplate

I have a UserControl that I have successfully been using as a header for presentations that involve a list which can be headered, using the xaml below:
<DockPanel >
<uc:ListSubjectHeader Subject="{Binding DisplayName}"
AddNewItemCommand="{Binding AddCommand}"
ImageSource="..." />
<!-- other controls -->
</DockPanel>
I would like to use this same control in another presentation where it would be the content for the header in a HeaderedContentControl, and came up with this xaml to do that:
<HeaderedContentControl Content="{Binding Path=DetailViewDepartment}" >
<HeaderedContentControl.HeaderTemplate>
<DataTemplate DataType="{x:Type vm:DepartmentSelectionViewModel}">
<uc:ListSubjectHeader Subject="{Binding DisplayName}" ... />
</DataTemplate>
</HeaderedContentControl.HeaderTemplate>
</HeaderedContentControl>
The visual elements show up the way I want them to, but data does not. I should note that I am using the same view model (vm:DepartmentSelectionViewModel) in a different control's DataTemplate in the same presentation, which I asked as a different question here. If you know the answer to this one you likely know the answer to that one too.
How can I fix this?
Cheers,
Berryl
The HeaderTemplate applies to the object in the Header property, not Content. Content uses the ContentTemplate, just like in the normal ContentControl.

Can I get strongly typed bindings in WPF/XAML?

Using the MVVM-pattern you set the DataContext to a specific ViewModel. Now is there any way to tell the XAML the type of the DataContext so that it will validate my bindings?
Looking for something like the typed viewdata in ASP.NET MVC.
You can write each individual binding in a strongly-typed way:
<TextBox Text="{Binding Path=(vm:Site.Contact).(vm:Contact.Name)}" />
However this would not validate the fact that TextBox DataContext is of type ViewModel.Site (and I think this is not possible, but I may be wrong).
No, the current spec does not have strong typing in Xaml. I believe that with .Net 4.0, Xaml should be seeing the capacity for generics. With that, I would think it should be much easier to have strong typing in Xaml.
No. FrameworkElement.DatatContext is the dependency property that enables data binding is of type object.
As pointed out by others, you can specify the expected type of a DataContext for a special template called a DataTemplate. Many controls such as ItemsControl, ControlControl provide access to DataTemplates to allow you to set the visual representation's expectations of the DataContext's type.
Bryan is correct, he did not test his code.
The correct application of a typed DataTemplate looks like this:
<Window>
<Window.Resources>
<DataTemplate x:Key="TypedTemplate" DataType="{x:Type myViewModel}">
...
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource TypedTemplate}" />
</Window>
ContentPresenter inherits directly from FrameworkElement and does not have a Template property. In addition, the Template property commonly refers to Control.Template of type ControlTemplate which is something entirely different than a DataTemplate.
I think Bryan was thinking of the ContentControl which is one of the two root control types (the other being ItemsControl). ContentControl does in fact inherit from Control. Therefore we can specify the Template property on it if we so choose.
<Window>
<Window.Resources>
<DataTemplate x:Key="TypedTemplate" DataType="{x:Type myViewModel}">
...
</DataTemplate>
<ControlTemplate x:Key="ControlSkin" TargetType="{x:Type ContentControl}">
...
</ControlTemplate>
</Window.Resources>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource TypedTemplate}" Template="{StaticResource ControlSkin}" />
</Window>
I personally declare a static PropertyPath for each property in my viewmodel the reference this using x:static as the binding path -
e.g
public class MyViewModel
{
public static PropertyPath MyPropertyPath = new PropertyPath("MyProperty");
public bool MyProperty{get; set;}
}
xaml : {Binding Path={x:Static local:MyViewModel.MyPropertyPath}}
This way all my bindings get validated on build.
Try this:
<Window>
<Window.Resources>
<DataTemplate x:Key="TypedTemplate" DataType="{x:Type myViewModel}">
...
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding}" Template="{StaticResource TypedTemplate}" />
</Window>
I haven't tested this code but it should give you the idea. The content presenter will display the current DataContext which will use the DataTemplate. This isn't strongly typed in the compiler but will throw a runtime error immediately on load (in the window's InitializeComponent). You should be able to catch this easily in your testing if something breaks.

Resources