Search code examples
wpfdata-bindinglistboxdatatemplatelistboxitem

Two ListBoxes with two (almost similar) templates


I have 2 Listboxes with 2 data templates that are almost identical except that one of them contains TextBox instead of ComboBox.

First Template :

<DataTemplate x:Key="OldPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
    <Grid Height="60" Margin="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="35"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
                Margin="0 0 2 0" >
            <TextBlock  Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" 
                        Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex), 
                Converter={StaticResource IncrementerConverter}}" />
        </Border>
        <TextBlock Grid.Column="1" Text="{Binding Name}" />
        <TextBlock Grid.Column="2" Text="{Binding DistributionSystemName}"/>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
        <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
    </Grid>
</DataTemplate>

Second Template :

<DataTemplate x:Key="NewPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
    <Grid Height="60">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="35"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
                Margin="0 0 2 0" >
            <TextBlock  Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" 
                        Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex), 
                Converter={StaticResource IncrementerConverter}}" />
        </Border>
        <TextBlock Grid.Column="1" Text="{Binding Name}" />
        <ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}" 
                  SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"  
                  IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}" 
                  ItemTemplate="{StaticResource DistributionSystemTemplate}" >
        </ComboBox>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="Number of available ways: "/>
        <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfAvailableWays}" />
    </Grid>
</DataTemplate>

As you can see they are both almost identical except for this part :

<ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}" 
                  SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"  
                  IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}" 
                  ItemTemplate="{StaticResource DistributionSystemTemplate}" >
        </ComboBox>

The problem is whenever i change anything in one of them i have to change the same thing in the other also ... Any way that can merge them in some way and make the combobox the only variable that changes according to which listbox is calling the template ?


Solution

  • Here an attempt on how you can achieve that, but I have to admit that it is messy but answers your question blindly! a better approach would be to properly implement a DataTemplateSelector.

    The idea is to decouple the changing parts into two separate DataTemplates and put them into the resources, one with the Combobox and one for the TextBlock in your case:

    <DataTemplate x:Key="DataTemplateCombobox">
            <ComboBox  ItemsSource="{Binding ValidDistributionSystemsForPanel}" ...>
            </ComboBox>
    </DataTemplate>
    <DataTemplate x:Key="DataTemplateTextblock" >
            <TextBlock Text="{Binding DistributionSystemName}" ... />
    </DataTemplate>
    

    Now those controls will be replaced with a ContentPresenter in your main (common) DataTemplate. The ContentPresenter uses a ContentTemplateSelector to select which sub-DataTemplate to used based on the name of the ListBox on which this DataTemplate is applied, this is why the Content is bound directly o the ListBox using ancestry binding:

     <local:ValueDataTemplateSelector x:Key="TemplateSelector" 
                                         DefaultDataTemplate="{StaticResource DataTemplateTextblock}" 
                                         ComboboxDataTemplate="{StaticResource DataTemplateCombobox}" 
                                         TextBlockDataTemplate="{StaticResource DataTemplateTextblock}" />
        <DataTemplate x:Key="OldPanelsTemplate">
            <Grid Height="60" Margin="0" Name="OldPanelsTemplateGrid" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="35"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
                        Margin="0 0 2 0" >
                    <TextBlock  Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" Text="tex" />
                </Border>
                <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
                <ContentPresenter ContentTemplateSelector="{StaticResource TemplateSelector}" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Grid.Row="0" Grid.Column="2">
    
                </ContentPresenter>
                <TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
                <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
            </Grid>
        </DataTemplate>
    

    And here how your DataTemplateSelector should be implemented (basically):

    public class ValueDataTemplateSelector : DataTemplateSelector
    {
        public DataTemplate DefaultDataTemplate { get; set; }
        public DataTemplate ComboboxDataTemplate { get; set; }
        public DataTemplate TextBlockDataTemplate { get; set; }
    
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var lb = item as ListBox;
    
            if (lb is null)
                return DefaultDataTemplate;
    
            if (lb.Name == "ListOne")
                return ComboboxDataTemplate;
            if (lb.Name == "ListTwo")
                return TextBlockDataTemplate;
            return DefaultDataTemplate;
        }
    }
    

    Finally, since your sub-DataTemplates lose their DataContexts due to the Content of the ContentPresenter being bound to the ListBox directly, then just hook their DataContexts again using ElementName Binding or something:

     <DataTemplate x:Key="DataTemplateCombobox">
            <ComboBox DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"  ItemsSource="{Binding ValidDistributionSystemsForPanel}" >
            </ComboBox>
        </DataTemplate>
        <DataTemplate x:Key="DataTemplateTextblock" >
            <TextBlock Text="{Binding DistributionSystemName}"  DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"/>
        </DataTemplate>
    

    OldPanelsTemplateGrid is the First Grid in the main DataTemplate that should have the valid ListBoxItem DataContext.

    Here is the full Xaml code:

       </Window ...
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800" >
    <Window.Resources>
        <DataTemplate x:Key="DataTemplateCombobox">
            <ComboBox DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"  ItemsSource="{Binding ValidDistributionSystemsForPanel}" >
            </ComboBox>
        </DataTemplate>
        <DataTemplate x:Key="DataTemplateTextblock" >
            <TextBlock Text="{Binding DistributionSystemName}"  DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"/>
        </DataTemplate>
        <local:ValueDataTemplateSelector x:Key="TemplateSelector" 
                                         DefaultDataTemplate="{StaticResource DataTemplateTextblock}" 
                                         ComboboxDataTemplate="{StaticResource DataTemplateCombobox}" 
                                         TextBlockDataTemplate="{StaticResource DataTemplateTextblock}" />
        <DataTemplate x:Key="OldPanelsTemplate">
            <Grid Height="60" Margin="0" Name="OldPanelsTemplateGrid" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="35"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
                        Margin="0 0 2 0" >
                    <TextBlock  Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" Text="tex" />
                </Border>
                <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
                <ContentPresenter ContentTemplateSelector="{StaticResource TemplateSelector}" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Grid.Row="0" Grid.Column="2">
    
                </ContentPresenter>
                <TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
                <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
            </Grid>
        </DataTemplate>
    
    </Window.Resources>
    <!--DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=2,AncestorType=DataTemplate}}"-->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
        <ListBox x:Name ="ListOne" ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource OldPanelsTemplate}"/>
        <ListBox x:Name ="LisTwo"  ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource OldPanelsTemplate}" Grid.Row="1"/>
    </Grid>