Reputation: 161
How can I set DataTemplate to a ContentControl in a PageResource? I would like to display a UserControl in my ContentControl and I want use the ContentControl like a navigation region. So it can change the UserControls what are displayed in it.
I have a Shell.xaml:
<Page
x:Class="MyProject.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:View="using:MyProject.View"
xmlns:ViewModel="using:MyProject.ViewModel">
<Page.DataContext>
<ViewModel:ShellViewModel />
</Page.DataContext>
<Page.Resources>
<DataTemplate>
<View:MyUserControlViewModel1 />
</DataTemplate>
<DataTemplate>
<View:MyUserControlViewModel2 />
</DataTemplate>
</Page.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Content="{Binding CurrentPageViewModel}">
</ContentControl>
</StackPanel>
</Page>
My Shell's view model is:
namespace MyProject.ShellViewModel
{
class ShellViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
#region Properties / Commands
public List<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
{
_pageViewModels = new List<IPageViewModel>();
}
return _pageViewModels;
}
}
public IPageViewModel CurrentPageViewModel
{
get { return _currentPageViewModel; }
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
public ShellViewModel()
{
PageViewModels.Add(new MyUserControlViewModel1());
PageViewModels.Add(new MyUserControlViewModel2());
CurrentPageViewModel = PageViewModels[0];
}
#endregion
}
}
I tried set Page.Resource like below from this link: Window vs Page vs UserControl for WPF navigation?
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView /> <!-- This is a UserControl -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView /> <!-- This is a UserControl -->
</DataTemplate>
</Window.Resources>
But these use another namespaces and it doesn't work for me because my app is a Windows 10 Universal App and there is no DataType attribute for the DataTemplate for example.
I am trying to make my application using MVVM pattern (if it was not obiously from the code snippets).
Upvotes: 4
Views: 1469
Reputation: 39072
You can do it by creating a class derived from DataTemplateSelector
.
In this class you can override SelectTemplateCore
method, that will return the DataTemplate
you need based on the data type. To make all this more auto-magical, you can set the key of each template to match the name of the class and then search for the resource with that name retrieved using GetType().Name
.
To be able to provide specific implementations on different levels, you can walk up the tree using VisualTreeHelper.GetParent()
until the matching resource is found and use Application.Current.Resources[ typeName ]
as fallback.
To use your custom template selector, just set it to ContentTemplateSelector
property of the ContentControl
.
Example
Here is the sample implementation of an AutoDataTemplateSelector
public class AutoDataTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore( object item ) => GetTemplateForItem( item, null );
protected override DataTemplate SelectTemplateCore( object item, DependencyObject container ) => GetTemplateForItem( item, container );
private DataTemplate GetTemplateForItem( object item, DependencyObject container )
{
if ( item != null )
{
var viewModelTypeName = item.GetType().Name;
var dataTemplateInTree = FindResourceKeyUpTree( viewModelTypeName, container );
//return or default to Application resource
return dataTemplateInTree ?? ( DataTemplate )Application.Current.Resources[ viewModelTypeName ];
}
return null;
}
/// <summary>
/// Tries to find the resources up the tree
/// </summary>
/// <param name="resourceKey">Key to find</param>
/// <param name="container">Current container</param>
/// <returns></returns>
private DataTemplate FindResourceKeyUpTree( string resourceKey, DependencyObject container )
{
var frameworkElement = container as FrameworkElement;
if ( frameworkElement != null )
{
if ( frameworkElement.Resources.ContainsKey( resourceKey ) )
{
return frameworkElement.Resources[ resourceKey ] as DataTemplate;
}
else
{
return FindResourceKeyUpTree( resourceKey, VisualTreeHelper.GetParent( frameworkElement ) );
}
}
return null;
}
}
You can now instantiate it as a resource and create resources for each type of ViewModel you use
<Application.Resources>
<local:AutoDataTemplateSelector x:Key="AutoDataTemplateSelector" />
<!-- sample viewmodel data templates -->
<DataTemplate x:Key="RedViewModel">
<Rectangle Width="100" Height="100" Fill="Red" />
</DataTemplate>
<DataTemplate x:Key="BlueViewModel">
<Rectangle Width="100" Height="100" Fill="Blue" />
</DataTemplate>
</Application.Resources>
And now you can use it with the ContentControl
as follows:
<ContentControl ContentTemplateSelector="{StaticResource AutoDataTemplateSelector}"
Content="{x:Bind CurrentViewModel, Mode=OneWay}" />
I have put the sample solution on GitHub
Upvotes: 4