Jasmeet
Jasmeet

Reputation: 1460

Change type of control based on type of item source?

I am working with MVVM design pattern where I want to change the type of control to be used based on the type of item source object. My code for item source types :

public class PropertyItemVM : ViewModelBase
{
   // Some base code for all basic  property items
}

public class StringValuePropertyItemVM : PropertyItemVM
{
   // Code to correctly create string value property item
}

public class ComboBoxPropertyItemVM : PropertyItemVM
{
   // Code to correctly create combo box property item
}

In my view model I have property list binded to xaml as

public ObservableCollection<PropertyItemVM> Properties { get; set; }

This collection contains both the types of objects StringValuePropertyItemVM and ComboBoxPropertyItemVM.

Now what I want to achieve is, based on type of object I want to decide whether the xaml will contain a TextBox or a ComboBox.

My xaml code :

<StackPanel>
    <!--Items Control containing all list of properties to be shown-->
    <ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                    </Grid.ColumnDefinitions>

                    <Label Content="{Binding Label}" 
                           IsEnabled="{Binding IsEnabled}"
                           Grid.Column="0"/>
                    <!-- Here based on type need to change the TextBox with ComboBox-->
                    <TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                             Grid.Column="1"
                             IsEnabled="{Binding IsEnabled}"/>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</StackPanel>

With some help I found out I can have different data templates for both types and switch among those, however I can't find how to do that. Can anyone help me with how can this be achieved ?

Upvotes: 0

Views: 500

Answers (1)

thatguy
thatguy

Reputation: 22089

Automatic Data Template Selection

Move the data templates to the ItemsControl resources and assign a DataType.

<StackPanel>
   <!--Items Control containing all list of properties to be shown-->
   <ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
      <ItemsControl.Resources>
         <DataTemplate DataType="{x:Type local:StringValuePropertyItemVM}">
            <Grid>
               <Grid.ColumnDefinitions>
                  <ColumnDefinition Width="100"/>
                  <ColumnDefinition Width="100"/>
               </Grid.ColumnDefinitions>

               <Label Content="{Binding Label}" 
                      IsEnabled="{Binding IsEnabled}"
                      Grid.Column="0"/>
               <!-- Here based on type need to change the TextBox with ComboBox-->
               <TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                        Grid.Column="1"
                        IsEnabled="{Binding IsEnabled}"/>
            </Grid>
         </DataTemplate>
         <DataTemplate DataType="{x:Type local:ComboBoxPropertyItemVM}">
            <ComboBox/>
         </DataTemplate>
      </ItemsControl.Resources>
   </ItemsControl>
</StackPanel>

The ItemsControl will automatically select the right data template based on the type.

Data Template Selector

Although you do not need it in this case, for more complex cases (e.g. conditions based on the view model), you could implement a DataTemplateSelector, like this:

public class PropertyDataTemplateSelector : DataTemplateSelector
{
   public override DataTemplate SelectTemplate(object item, DependencyObject container)
   {
      if (!(container is FrameworkElement frameworkElement))
         return null;

      switch (item)
      {
         case StringValuePropertyItemVM _:
            return (DataTemplate)frameworkElement.FindResource("StringValuePropertyItemVMDataTemplate");
         case ComboBoxPropertyItemVM _:
            return (DataTemplate)frameworkElement.FindResource("ComboBoxPropertyItemVMDataTemplate");
         default:
            return null;
      }
   }
}

Then you would define the data templates in a resource dictionary in scope with keys.

<DataTemplate x:Key="StringValuePropertyItemVMDataTemplate" DataType="{x:Type local:StringValuePropertyItemVM}">
   <Grid>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="100"/>
         <ColumnDefinition Width="100"/>
      </Grid.ColumnDefinitions>

      <Label Content="{Binding Label}" 
             IsEnabled="{Binding IsEnabled}"
             Grid.Column="0"/>
      <!-- Here based on type need to change the TextBox with ComboBox-->
      <TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
               Grid.Column="1"
               IsEnabled="{Binding IsEnabled}"/>
   </Grid>
</DataTemplate>
<DataTemplate x:Key="ComboBoxPropertyItemVMDataTemplate" DataType="{x:Type local:ComboBoxPropertyItemVM}">
   <ComboBox/>
</DataTemplate>

Finally, you would assign or reference an instance of the selector to the ItemsControl.

<StackPanel>
   <!--Items Control containing all list of properties to be shown-->
   <ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
      <ItemsControl.ItemTemplateSelector>
         <local:PropertyDataTemplateSelector/>
      </ItemsControl.ItemTemplateSelector>
   </ItemsControl>
</StackPanel>

Upvotes: 1

Related Questions