I'm trying to create a DataTemplate for a View, to show a specific UserControl type (like a texbox, combobox, custom control or another View) based on the type of object it is bound to.
I have the following MVVM framework:
FieldView
is tied to an instance of FieldPresenter
, and should display a <Textblock />
for the "Label" property, and a UserControl or another View for the Value (based on the Type of the value), with it's DataSource set to the Value property of the Presenter. Currently, I do not have the second part working. I can't figure out how to write a WPF template for what I need.
ViewModel:
public class FieldPresenter : Observable<object>, IFieldPresenter, INotifyPropertyChanged
{
public FieldPresenter() { }
public FieldPresenter(object value)
{
Value = value;
}
object IFieldPresenter.Value
{
get
{
return base.Value;
}
set
{
base.Value = value;
OnPropertyChanged("Value");
}
}
private string _label;
public virtual string Label
{
get
{
return _label;
}
private set
{
_label = value;
OnPropertyChanged("Label");
}
}
}
View:
<UserControl x:Class="My.Views.FieldView"
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:My.ViewModels"
mc:Ignorable="d"
d:DesignHeight="24" d:DesignWidth="100">
<UserControl.DataContext>
<ViewModels:FieldPresenter/>
</UserControl.DataContext>
<UserControl.Template>
<ControlTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,0,0" HorizontalAlignment="Stretch" Width="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth}">
<TextBlock Text="{Binding Label}" FontWeight="Bold" Height="32" HorizontalAlignment="Stretch"/>
<TextBox Text="{Binding Value}" Height="Auto" HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
I'm curious if what I'm trying to do is even possible, or if I can workaround it by making my Presenter viewmodel return a UserControl rather than an object value, and have the Presenter parse the UserControl Type from the object type, but I don't feel like my Presenter should be instantiating Controls (or what is technically an unbound view). Should I make an interface, something like IViewAs<controlType> { controlType View { get; } }
?
How else would I replace <TextBox Text="{Binding Value}" />
in the above script with some kind of template of a UserControl based on the databound object's type?
You almost certainly want a ContentTemplateSelector
:
Code:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Primitive primitive;
primitive = new Sphere();
// primitive = new Cube();
DataContext = primitive;
}
}
internal abstract class Primitive
{
public abstract string Description { get; }
}
internal class Cube : Primitive
{
public override string Description
{
get { return "Cube"; }
}
}
internal class Sphere : Primitive
{
public override string Description
{
get { return "Sphere"; }
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var frameworkElement = container as FrameworkElement;
if (frameworkElement != null && item != null)
{
if (item is Cube)
{
return frameworkElement.FindResource("CubeTemplate") as DataTemplate;
}
if (item is Sphere)
{
return frameworkElement.FindResource("SphereTemplate") as DataTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<local:MyTemplateSelector x:Key="myTemplateSelector" />
<DataTemplate x:Key="CubeTemplate" DataType="local:Cube">
<Border BorderBrush="Blue"
BorderThickness="1"
CornerRadius="5" />
</DataTemplate>
<DataTemplate x:Key="SphereTemplate" DataType="local:Sphere">
<Border BorderBrush="Red"
BorderThickness="1"
CornerRadius="50" />
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0"
Content="{Binding Description}"
d:DataContext="{d:DesignInstance local:Primitive}" />
<ContentControl Grid.Row="1"
Content="{Binding}"
ContentTemplateSelector="{StaticResource myTemplateSelector}" />
</Grid>
</Window>
Result:
See the documentation for more:
https://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector(v=vs.110).aspx