Search code examples
wpfxamlmvvmbindingdatatemplate

DataTemplate Binding depending on property type and with working property Binding


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!


Solution

  • 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}">