Sharing DataTemplates between controls - wpf

I have a ContentPresenter in a couple of places in my application with exactly the same DataTemplates. For now I simply copy-pasted them, but I'd like to clean that up and share them between ContentPresenter instances. I tried this approach:
<ContentControl Content="{Binding DataEditorViewModel}">
<ContentControl.Resources>
<ResourceDictionary Source="pack://application:,,,/LogAnalyzer;component/PredicateDataEditors.xaml" />
</ContentControl.Resources>
</ContentControl>
Application runs, but DataTemplates aren't being applied, I simply see name of class being ContentPresenter's content instead of defined template. I put templates in ResourceDictionary in the following way:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LogAnalyzer"
xmlns:p="clr-namespace:LogAnalyzer.BusinessLogic.ViewModels.Processing;assembly=LogAnalyzer.BusinessLogic"
xmlns:xwt="http://schemas.xceed.com/wpf/xaml/toolkit">
<DataTemplate DataType="{x:Type p:MessageRuleDataEditorViewModel}" x:Key="{x:Type p:MessageRuleDataEditorViewModel}">
(...)
</DataTemplate>
(...)
</ResourceDictionary>
What should I do to embed DataTemplates correctly in ContentPresenter's resources?

if you define this data template in your app.xaml:
<DataTemplate DataType="{x:Type p:MessageRuleDataEditorViewModel}">
<TextBlock Text="testing" />
</DataTemplate>
and then do something like:
<ListBox ItemsSource="{Binding MyCollectionOfMessageRuleDataEditorViewModels}"/>
then your data template is automatically applied to every object of that same type. dont define a key for your global templates

Related

StaticResource found when added during runtime, but not on launch

I have this parent control that displays its CurrentViewModel, and instance of ViewModelWithExam, using the data templates provided.
<UserControl x:Class="Gui.Views.ExamsTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:views="clr-namespace:Gui.Views.Tabs.ExamsTabViews"
xmlns:viewmodels="clr-namespace:Gui.ViewModels"
xmlns:data="clr-namespace:DataManagement;assembly=DataManagement"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:ExamInfoViewModel}">
<views:ExamInfoView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamAcquireImageViewModel}">
<views:ExamAcquireImageView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamEditImageViewModel}">
<views:ExamEditImageView/>
</DataTemplate>
<!--Reuseable control to display the simple exam header-->
<DataTemplate x:Key="ExamHeader" DataType="{x:Type data:Exam}">
<views:ExamHeaderView/>
</DataTemplate>
</UserControl.Resources>
<Grid VerticalAlignment="Stretch" Margin="20">
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</UserControl>
Any of the views can use the ExamHeader to display their Exam property:
<ContentControl ContentTemplate="{StaticResource ExamHeader}" Content="{Binding Exam}"/>
The ExamHeader template, however, exhibits odd behavior. If I remove the x:Key property from the template, and remove the ContentTemplate property from the ContentControl, and then launch the app, the header displays, as expected.
While the app is running, if I add back the x:Key, the header disappears and is replaced by the classname of the Exam. As expected. If I then add back the ContentTemplate to point to the header template, the header template shows. It works.
However, if I launch the app with the x:Key and ContentTemplate in place, then loading a page with a header puts the application in break mode, saying "The resource 'ExamHeader' could not be found." Indeed, when the app isn't running, this error appears under the ControlTemplate property.
But, to say again, if I add in the x:Key and ContentTemplate while the app is running, it work fine!
How do I fix this?
It might be a resource ordering issue. Try moving the ExamHeader resource to the top of your resources:
<UserControl.Resources>
<!--Reuseable control to display the simple exam header-->
<DataTemplate x:Key="ExamHeader" DataType="{x:Type data:Exam}">
<views:ExamHeaderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamInfoViewModel}">
<views:ExamInfoView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamAcquireImageViewModel}">
<views:ExamAcquireImageView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamEditImageViewModel}">
<views:ExamEditImageView/>
</DataTemplate>
</UserControl.Resources>

Include shared resource in ResourceDictionaries for multiple controls

I have a DataTemplate I'd like to use with multiple ContentControls. But each of these controls needs to contain additional DataTemplates as well, and the final list is different for each one. Something like this:
<DataTemplate x:Key="FooDataTemplate" DataType="{x:Type Foo}" />
<!-- snip -->
<ContentControl>
<ContentControl.Resources>
<DataTemplate DataType="{x:Type Bar}" />
<!-- Also want a copy of FooDataTemplate here, but without an explicit key -->
</ContentControl.Resources>
</ContentControl>
<ContentControl>
<ContentControl.Resources>
<DataTemplate DataType="{x:Type Baz}" />
<!-- Also want a copy of FooDataTemplate here, but without an explicit key -->
</ContentControl.Resources>
</ContentControl>
Is there a XAML syntax that will let me do this? I could create an "AddResourceToDictionary" attached behavior or put FooDataTemplate in its own resource dictionary file and merge it in, but both seem like an awful lot of hassle for a simple thing...
You could add FooDataTemplate to the ContentControl's ResourceDictionary, provided that it's in scope:
<ContentControl>
<ContentControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type Bar}" />
<StaticResource ResourceKey="FooDataTemplate" />
</ResourceDictionary>
</ContentControl.Resources>
</ContentControl>
But if you want a "copy of FooDataTemplate, but without an explicit key", you need to create a new DataTemplate yourself. There is no XAML syntax that lets you base a DataTemplate on another one. A template must be defined as a whole. So I guess the answer to your question is simply no.
FooDataTemplate can only be referenced and used as-is. And since it's already in scope, it doesn't really make any sense adding it to the ContentControl's ResourceDictionary.

dynamically accessing datatemplates

I have the following xaml: animationTemplate switches between template1 and template2.
I would like to make this more reusable by putting the animationTemplate in the resource dictionary so multiple controls can use the same animationTemplate. The problem is that animation template has references to template1 and template2 so if I put the animationTemplate in the dictionary I get an error because animationTemplate cant find templates 1 and 2.
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate x:Key="template1" />
<DataTemplate x:Key="template2" />
<DataTemplate x:Key="animationTemplate" />
</ResourceDictionary>
</Window.Resources>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource animationTemplate}" />
Is there a better way other then putting all of the template pairs in the dictionary and using a contentTemplateSelector to pick from them? I'd rather not do this (even if its possible because I have event handlers in the templates and I'm not sure how I would remove them)

implicit DataTemplates vs. sample data vs. blendability

I have two simple ViewModels, NodeViewModel and LeafViewModel that can be items in a TreeView. Just like below. Templates are applied implictly because I don't want a custom template selector.
<UserControl x:Class="MyProject.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ViewModels="clr-namespace:MyProject.ViewModels" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignData /SampleData/NodeViewModelSampleData.xaml}">
<UserControl.Resources>
<HierarchicalDataTemplate DataType="{x:Type ViewModels:NodeViewModel}" ItemsSource={Binding Children}>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}" IsChecked="{Binding Result}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type ViewModels:LeafViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>
<TreeView ItemsSource="{Binding Children}" />
</UserControl>
How can I generate sample data in blend that contains a tree with both NodeViewModels and LeafViewModels and then display it as the data in the treeview while still using implict template selection?
In the absence of using some kind of mocking framework, I've found that the easiest way to do this is to just hack together a class that generates instances of my view models and use it as a data source in Blend.
It occurs to me that it might be even easier to just define the test data in XAML, though this is contingent on the view model classes being designed to allow that (e.g. with parameterless constructors and a ContentProperty attribute, among other things).
I think the answer is simple: You can't.
Blend doesn't really work well with implicit datatemplates and template selectors. This is not only true for sample data but also inplace wysiwyg template editing. So for blendability you should try to avoid implict templates and template selectors whenever you can.

Use DataTemplate in WPF with a mocked object

I have following xaml code:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding MainWindow, Source={StaticResource Locator}}">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:KeyboardViewModel}">
<vw:Keyboard />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:WelcomeViewModel}">
<vw:Welcome />
</DataTemplate>
</Window.Resources>
<DockPanel>
<DockPanel>
<ContentControl Content="{Binding Path=Workspace}" />
</DockPanel>
</DockPanel>
</Window>
When Workspace is KeyboardViewModel, then the UserControl Keyboard is shown. When Workspace is Welcome, then the Welcome screen is shown. But when I test I mock the ViewModels with Moq. Workspace then get the type IKeyboardViewModelProxyxxxxxxxxxxxxx (where xxxxxxx is a random string), that don't maps to KeyboardViewModel in the DataTemplate and WPF don't now wish DataTemplate to show.
When I use the real KeyboardViewModel, it is no problem.
Can I fix it somehow, or do I have to redesign it?
I'm having a similar issue (without using Moq however). A PARTIAL solution that I used is to inherit both KeyboardViewModel and KeyboardViewModelMock from abstract KeyboardViewModelAbstract. Then you can do:
<DataTemplate DataType="{x:Type vm:KeyboardViewModelAbstract}">
<vw:Keyboard />
</DataTemplate>
Which will work for both, the real model object and the mock.
Unfortunately this solution doesn't scale when you're dealing with models that already have a base class or have any kind of inheritance involved. I'd be great if DataTemplate could be used with interfaces, but they can't.
You can omit the DataType="{x:Type vm:KeyboardViewModel}". If you do that, it is not expecting an instance of type KeyboardViewModel to bind against anymore but only an object of any type that just has all properties that are used in the template.

Resources