I have an ItemsControl that binds to a list of items. These items have a name and value property. The value property is of type Object to allow different datatypes to be used. To display the value property correctly I use a ContentPresenter with a datatemplate for every datatype I might use.
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Name}"/>
<GridSplitter Width="1"
Grid.RowSpan="4" Grid.Column="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<ContentPresenter Grid.Column="2" Content="{Binding Value}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}"
BorderThickness="0"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Int32}">
<TextBox Text="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}"
TextAlignment="Right"
BorderThickness="0"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Double}">
<TextBox Text="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}"
TextAlignment="Right"
BorderThickness="0"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Boolean}">
<CheckBox IsChecked="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}"
HorizontalAlignment="Center"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ContentPresenter uses the correct datatype and works great. My problem is that editing these values does not have any effect on the bound items. I suspect it is because I bind to the content property of the ContentPresenter rather than directly to the Value. I've tried using the ContentPresenter like this:
<ContentPresenter Grid.Column="2" Content="{Binding}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Value}"
BorderThickness="0"/>
</DataTemplate>
But this way the correct DataTemplate isn't selected and it just displays the Object instead of a String for example. I also tried to leave out the path in the binding of the DataTemplate like this:
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding}" BorderThickness="0"/>
</DataTemplate>
With this I get an exception telling me to use the Path or XPath attribute.
So my question is: how do I correctly bind to the Value so it displays with the right DataTemplate and that any editing of the values is applied to the bound item.
Btw for some reason the formatted code blocks in my question indent much more after the first line. I tried fixing it but I don't understand what's happening.
Already being uncomfortable with my solution I also ran into the problem of not being able to add a List DataType to the DataTemplates. I ended up using a DataTemplateSelector which resulted in much nicer code. Here it is:
The ContentControl. A container for the data which the DataTemplate is applied over:
<ContentControl Grid.Column="2" Content="{Binding}"
ContentTemplateSelector="{StaticResource propertyItemTemplateSelector}">
</ContentControl>
A few DataTemplates and a declaration for the DataTemplateSelector:
<Style.Resources>
<local:PropertyTemplateSelector x:Key="propertyItemTemplateSelector"/>
<DataTemplate x:Key="dtStringValue">
<TextBox Text="{Binding Path=Value}"
BorderThickness="0"
IsReadOnly="{Binding Path=IsReadOnly}">
</TextBox>
</DataTemplate>
<DataTemplate x:Key="dtIntegerValue">
<TextBox Text="{Binding Path=Value}"
TextAlignment="Right"
BorderThickness="0"
IsReadOnly="{Binding Path=IsReadOnly}"/>
</DataTemplate>
...
The code for the DataTemplateSelector:
public class PropertyTemplateSelector : DataTemplateSelector
{
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
DataTemplate template = null;
IPropertyItem propertyItem = item as IPropertyItem;
if (propertyItem != null)
{
FrameworkElement element = container as FrameworkElement;
if (element != null)
{
var value = propertyItem.Value;
if (value is String)
{
template = element.FindResource("dtStringValue") as DataTemplate;
}
else if (value is Int32)
{
template = element.FindResource("dtIntegerValue") as DataTemplate;
}
....