1
votes

I check those articles about doing DataTemplate :

and thoses about DataTemplate depending on property type :

I'm trying to display a property with different controls depending of the property value. I have this Xaml that is partialy working. I have 2 problems :

  1. The property is displaying with the right control, but when I set the value it doesn't go back to the property. Means the "set" of My property is not call (but was before I creates the DataTemplate). I detect that the problem about setting the property is about the ="{Binding Path=.}" but I cannot find the solution to set it otherwise.

  2. Also To be able to make it work, I had to "isolate" the Value into a single ViewModel so that the DataTemplate doesn't affect all the other control.

Can you help me find betters solutions to resolves those 2 problems?

Here is the xaml code of my View linked with MyContainerViewModel that has a "ChangingDataType" :

<UserControl >
<UserControl.Resources>
    <!-- DataTemplate for strings -->
    <DataTemplate DataType="{x:Type sys:String}">
        <TextBox Text="{Binding Path=.}" HorizontalAlignment="Stretch"/>
    </DataTemplate>
    <!-- DataTemplate for bool -->
    <DataTemplate DataType="{x:Type sys:Boolean}">
        <CheckBox IsChecked="{Binding Path=.}" />
    </DataTemplate>
    <!-- DataTemplate for Int32 -->
    <DataTemplate DataType="{x:Type sys:Int32}">
        <dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="d" MaskType="Numeric" HorizontalAlignment="Stretch"/>
        <!--<Slider Maximum="100" Minimum="0" Value="{Binding Path=.}" Width="100" />-->
    </DataTemplate>
    <!-- DataTemplate for decimals -->
    <DataTemplate DataType="{x:Type sys:Decimal}">
        <!-- <TextBox Text="{Binding Path=.}" MinWidth="50" HorizontalAlignment="Stretch" />-->
        <dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="f" MaskType="Numeric" HorizontalAlignment="Stretch" />
    </DataTemplate>
    <!-- DataTemplate for DateTimes -->
    <DataTemplate DataType="{x:Type sys:DateTime}">
        <DataTemplate.Resources>
            <DataTemplate DataType="{x:Type sys:String}">
                <TextBlock Text="{Binding Path=.}"/>
            </DataTemplate>
        </DataTemplate.Resources>
        <DatePicker SelectedDate="{Binding Path=.}" HorizontalAlignment="Stretch"/>
    </DataTemplate>
    </UserControl.Resources>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</UserControl>

More informations about 2 :

I wanted to have in a view a label and a property that changes depending of the object. Something like this :

<UserControl> 
    <UserControl.Resources>
        <!-- ...DataTemplate here... -->
    </UserControl.Resources>
    <StackPanel>
        <Label Content="Allo"/>
        <ContentPresenter Content="{Binding MyChangingPropery}"/>
    </StackPanel>
</UserControl>

But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo". So I had to create another view that contains the DataTemplate and MyChangingProperty so that the label Allo would not be affected. But the extra View created just for one property is kind of ugly to me, I'm sure there is a better way to isolate the DataTemplate so it can apply only to one UIControl.

<UserControl >
    <StackPanel>
        <Label Content="Allo"/>
        <ContentPresenter Content="{Binding MyContainerViewModel}"/>
    </StackPanel>
</UserControl>

Note : MyContainerViewModel here is linked with the first view described.

Thanks in advance!

2
Can you please explain what problem number 2 is? The scope of a resource in WPF is by design.haindl
Sure I will add more information on my question for the number 2.Rachel

2 Answers

1
votes

One possible solution would be to use a DataTemplateSelector. You cannot bind primitive types using two way bindings because that would have to be somehow by reference via the DataTemplate which I think is not supported by WPF.

The DataTemplateSelector now selects the right DataTemplate based on the property type and searches for the right DataTemplate in the resources by name. This also solves your problem that your DataTemplates interacted with the Label.

So first you need to define a DataTemplateSelector that changes the DataTemplate based on the type of the property:

public class MyDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var fe = (FrameworkElement)container;
        var prop = (item as MyViewModelType)?.MyChangingProperty;
        if (prop is string)
            return fe.FindResource("MyStringDT") as DataTemplate;
        else if (prop is bool)
            return fe.FindResource("MyBoolDT") as DataTemplate;
        // More types...
        return base.SelectTemplate(item, container);
    }
}

Then you need to change the UserControl like this:

<UserControl>
    <UserControl.Resources>
        <local:MyDataTemplateSelector x:Key="MyDTSelector" />
        <!-- DataTemplate for strings -->
        <DataTemplate x:Key="MyStringDT">
            <TextBox Text="{Binding MyChangingProperty, Mode=TwoWay}"
                HorizontalAlignment="Stretch"/>
        </DataTemplate>
        <!-- DataTemplate for bool -->
        <DataTemplate x:Key="MyBoolDT">
            <CheckBox IsChecked="{Binding MyChangingProperty, Mode=TwoWay}" />
        <!-- More DataTemplates... -->
        </DataTemplate>
    </UserControl.Resources>
    <StackPanel>
        <Label Content="Allo"/>
        <ContentPresenter Content="{Binding MyContainerViewModel}"
            ContentTemplateSelector="{StaticResource MyDTSelector}" />
    </StackPanel>
</UserControl>

You can find a bit more information regarding the DataTemplateSelector here.

You can of course also set a DataType on this new DataTemplates but it isn't required because the x:Key makes them unique anyway. But if you want then it has to look like this:

<DataTemplate x:Key="MyStringDT" DataType="{x:Type local:MyViewModelType}">
0
votes

In my opinion, the previously posted answer is overkill. While a DateTemplateSelector is a useful thing to know about, it seems unnecessary to me in this scenario.

But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo".

The reason it affects the Label object is that the Label object is a ContentControl, and so does the same template-matching behavior for content types as your own ContentPresenter element does. And you've set the content of the Label object to a string value. But you can put anything you want as the content for it.

The way to fix the undesired effect is to intercept that behavior by changing the content from a string object to an explicit TextBlock (the control in the template that a string object normally gets assigned). For example:

<UserControl> 
    <UserControl.Resources>
        <!-- ...DataTemplate here... -->
    </UserControl.Resources>
    <StackPanel>
        <Label>
            <TextBlock Text="Allo"/>
        </Label>
        <ContentPresenter Content="{Binding MyChangingPropery}"/>
    </StackPanel>
</UserControl>

In that way, you bypass the template-finding behavior (since TextBlock doesn't map to any template and can be used directly), and the content for the Label will just be the TextBlock with the text you want.

This seems like a lot simpler way to fix the issue, than either to create a whole new view or to add a DataTemplateSelector.