Search code examples
c#wpfmvvmdata-binding

TabControl/TabItems bindings


I have a TabControl that retrieves its TabItems from a list of objects. Each TabItem has a header that is linked to the "Name" property of the corresponding object. The content of each TabItem is connected to the "Settings" property, which contains additional properties.

To achieve different behavior depending on the type of a property called "type," I use data triggers. For example, if the "type" property is set to "int," a TextBox is displayed, while if it's set to "bool," a CheckBox is displayed instead.

However, I am encountering an issue where this behavior only works for one specific type within the list. For instance, only one type, such as "string," correctly displays the TextBox, and only one "bool" property correctly displays the CheckBox. I am unable to determine the cause of this issue.

View Code:

<TabControl ItemsSource="{Binding ItemsSource}" >
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ItemsControl ItemsSource="{Binding Settings}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>

                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{Binding Name}" FontSize="17" Width="100" />
                                <ContentControl>
                                    <ContentControl.Style>
                                        <Style TargetType="ContentControl">
                                            <Style.Triggers>
                                                <DataTrigger Binding="{Binding Type}" Value="string">
                                                    <Setter Property="Content">
                                                        <Setter.Value>
                                                            <TextBox Text="{Binding Value}" FontSize="17" Width="80" Margin="5"/>
                                                        </Setter.Value>
                                                    </Setter>
                                                </DataTrigger>

                                                <DataTrigger Binding="{Binding Type}" Value="int">
                                                    <Setter Property="Content">
                                                        <Setter.Value>
                                                            <TextBox Text="{Binding Value}"  Width="50" FontSize="17" Margin="5"/>
                                                        </Setter.Value>
                                                    </Setter>
                                                </DataTrigger>

                                                <DataTrigger Binding="{Binding Type}" Value="bool">
                                                    <Setter Property="Content">
                                                        <Setter.Value>
                                                            <CheckBox IsChecked="{Binding Value}" VerticalAlignment="Center" Margin="5"/>
                                                        </Setter.Value>
                                                    </Setter>
                                                </DataTrigger>
                                            </Style.Triggers>
                                        </Style>
                                    </ContentControl.Style>
                                </ContentControl>
                            </StackPanel>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>

However, If I avoid the DataTriggers and I use only TextBox for all types, it works:

<TabControl TabStripPlacement="Left" ItemsSource="{Binding ItemsSource.Groups}" >
        <TabControl.ItemContainerStyle>
            <Style TargetType="TabItem" BasedOn="{StaticResource BaseTabItemsStyle}" />
        </TabControl.ItemContainerStyle>

        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ItemsControl ItemsSource="{Binding Settings}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{Binding Name}" FontSize="17" />
                                <TextBox Text="{Binding Value}" FontSize="17" />
                                
                            </StackPanel>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl> 

ViewModel Code:

public class MainWindowViewModel : BindableBase
    {

        // My property
        private ObservableCollection<Group> itemsSource;
        public ObservableCollection<Group> ItemsSource
        {
            get { return itemsSource; }
            set { SetProperty(ref itemsSource, value); }
        }


        // Constructor
        public MainWindowViewModel()
        {
            SetConfigValues();
        }

        private void SetConfigValues()
        {
            // Create some settings
            var _settings1 = new Setting()
            {
                Name = "Host",
                Value = "localhost",
                Type = "string"
            };
            var _settings2 = new Setting()
            {
                Name = "Port",
                Value = "5432",
                Type = "int"
            };
            var _settings3 = new Setting()
            {
                Name = "AutoStart",
                Value = "true",
                Type = "bool"
            };

            // Set these settings to Items source
            ItemsSource = new ObservableCollection<Group>()
            {

                new Group()
                {
                    Name = "Header Tab 1",
                    Settings = new ObservableCollection<Setting>
                    {
                        _settings1,
                        _settings2,
                        _settings3,
                        _settings1,
                        _settings2,
                        _settings3
                    },
                },

                new Group()
                {
                    Name = "Header Tab 2",
                    Settings = new ObservableCollection<Setting>
                    {
                        _settings1,
                        _settings2,
                        _settings3,
                        _settings1,
                        _settings2,
                        _settings3
                    },
                }
            };
        }

    }

My Custom Classes (Where data comes from):

public class Group
    {
        public string Name { get; set; }
        public ObservableCollection<Setting> Settings { get; set; }
    }

    public class Setting
    {
        public string Type { get; set; }
        public string Value { get; set; }
        public string Name { get; set; }
    }

What I get:

enter image description here

What I expect: enter image description here


Solution

  • Define a DataTemplate per type and set the ContentTemplate property in your Style:

    <ItemsControl ItemsSource="{Binding Settings}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <DataTemplate.Resources>
                    <DataTemplate x:Key="stringTemplate">
                        <TextBox Text="{Binding Value}" FontSize="17" Width="80" Margin="5"/>
                    </DataTemplate>
                    <DataTemplate x:Key="intTemplate">
                        <TextBox Text="{Binding Value}"  Width="50" FontSize="17" Margin="5"/>
                    </DataTemplate>
                    <DataTemplate x:Key="boolTemplate">
                        <CheckBox IsChecked="{Binding Value}" VerticalAlignment="Center" Margin="5"/>
                    </DataTemplate>
                </DataTemplate.Resources>
    
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Name}" FontSize="17" Width="100" />
                    <ContentControl Content="{Binding}">
                        <ContentControl.Style>
                            <Style TargetType="ContentControl">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Type}" Value="string">
                                        <Setter Property="ContentTemplate" Value="{StaticResource stringTemplate}"/>
                                    </DataTrigger>
    
                                    <DataTrigger Binding="{Binding Type}" Value="int">
                                        <Setter Property="ContentTemplate" Value="{StaticResource intTemplate}"/>
                                    </DataTrigger>
    
                                    <DataTrigger Binding="{Binding Type}" Value="bool">
                                        <Setter Property="ContentTemplate" Value="{StaticResource boolTemplate}"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </ContentControl.Style>
                    </ContentControl>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Also note that I've set/bound the Content property of the ContentControl to the Setting object. A ContentControl is supposed to have some Content and a ContentTemplate to visualize this content.