Search code examples
c#wpfdata-bindingdatatemplatecontentcontrol

ContentControl DataTemplate binding to primitive types


I have a viewmodel with a propety that can be a primitive type:

class VM : VMBase // implements INotifyProperty
{
    // can be float, int or bool (System.Single, System.Int32, System.Boolean)
    private object _Value;
    public object Value {
        get{return _Value;}
        set{
            if(_Value!=value){
                _Value=value;
                OnPropertyChanged(nameof(Value)); // notify bindings
            }
        }
    }
}

The type of Value can be float, int or bool. Now I'd like to display a TextBox if Value's type is float or int and a CheckBox if it's a bool. In XAML I try this:

<Grid> <!-- DataContext is VM -->
   <Grid.Resources>
      <DataTemplate DataType="{x:Type sys:Single}">
         <TextBox Width="64" Text="{Binding Path=DataContext.Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}}"/>
      </DataTemplate>
      <DataTemplate DataType="{x:Type sys:Int32}">
         <TextBox Width="64"/>
      </DataTemplate>
      <DataTemplate DataType="{x:Type sys:Boolean}">
         <CheckBox Width="64"/>
      </DataTemplate>
   </Grid.Resources>
      <ContentControl Content="{Binding Value}" IsTabStop="False"/>
   </Grid>

It displays the controls correctly for each type. But I'm unable to use the binding to modify the value of Value property by typing a new value in its text box. How should I make a two-way binding? Is there a better method to do what I'm trying to do?


Solution

  • As you've correctly spotted you need to specify a Template for each view of the data, the part that you are missing is that you can then use a DataTemplateSelector to choose the correct one

    eg

    code

    public class CustomTemplateSelector : DataTemplateSelector
    {
        public DataTemplate Template1{get;set;}
        public DataTemplate Template2{get;set;}
        public DataTemplate Template3{get;set;}
        public DataTemplate Template4{get;set;}
    
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if(item is float)
                return Template1;
            else if(item is int)
                return Template1;
            else if(item is string)
                return Template2;
            else if(item is bool)
                return Template3;
            //etc
            else
                return Template4;
        }
    }
    

    xaml

    <local:CustomeDataTemplate>
        <local:CustomeDataTemplate.Template1>
            <DataTemplate>
                <TextBox Text="{Binding }" />
            </DataTemplate>
        </local:CustomeDataTemplate.Template1>
        <local:CustomeDataTemplate.Template2>
            <DataTemplate>
                <CheckBox IsChecked="{Binding }"/>
            </DataTemplate>
        </local:CustomeDataTemplate.Template2>
    </local:CustomeDataTemplate>
    

    remember to bind the content of the template to the data