两个带有两个(几乎相似)模板的列表框

Two ListBoxes with two (almost similar) templates

我有 2 个列表框和 2 个几乎相同的数据模板,除了其中一个包含 TextBox 而不是 ComboBox。

第一个模板:

<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>

第二个模板:

<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>

如您所见,除了这一部分外,它们几乎完全相同:

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

问题是每当我更改其中一个中的任何内容时,我也必须在另一个中更改相同的内容...任何可以以某种方式合并它们并使组合框成为唯一根据以下内容更改的变量的方法哪个列表框在调用模板?

这里尝试如何实现,但我不得不承认它很乱,但盲目地回答了你的问题!更好的方法是正确实施 DataTemplateSelector.

我们的想法是将变化的部分分离为两个单独的 DataTemplates 并将它们放入资源中,一个带有 Combobox ,另一个用于 TextBlock 在您的情况下:

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

现在,这些控件将在您的主(通用)DataTemplate 中替换为 ContentPresenterContentPresenter 使用 ContentTemplateSelector 到 select 根据应用此 DataTemplateListBox 的名称使用哪个子数据模板,这就是为什么Content 使用祖先绑定直接绑定到 ListBox

 <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>

以及您的 DataTemplateSelector 应该如何实施(基本上):

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;
    }
}

最后,由于 ContentPresenterContent 直接绑定到 ListBox,您的子 DataTemplates 丢失了它们的 DataContext,因此只需挂钩它们的 DataContext s 再次使用 ElementName 绑定或其他东西:

 <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 是主 DataTemplate 中的第一个网格,应该具有有效的 ListBoxItem DataContext.

这是完整的 Xaml 代码:

   </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>

我设法通过将它们合并到一个模板中来解决这个问题:

    <DataTemplate x:Key="NewOldPanelsTemplate" 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}" />
        <!-- To have same template for new and old panels we had the two elements (combobox and textblock) for distribution system and toggle visibility by converters according to their groupbox title -->
        <ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}" 
                  SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"  
                  IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}" 
                  ItemTemplate="{StaticResource DistributionSystemTemplate}" 
                  Visibility="{Binding RelativeSource={RelativeSource AncestorType=GroupBox, Mode=FindAncestor}, Path=Header,Converter={StaticResource NewPanelsTemplateVisibilityConverter}}">
        </ComboBox>
        <TextBlock Grid.Column="2" Text="{Binding DistributionSystemName}"
                   Visibility="{Binding RelativeSource={RelativeSource AncestorType=GroupBox, Mode=FindAncestor}, Path=Header,Converter={StaticResource OldPanelsTemplateVisibilityConverter}}"/>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="Number of available ways: "/>
        <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfAvailableWays}" />
    </Grid>

</DataTemplate>

并使用这样的转换器控制可变部分(在我的例子中是 ComboBox 和 TextBlock)的可见性:

 public class OldPanelsTemplateVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((string)value == "Old Panels")
        {
            return Visibility.Visible;
        }
        return Visibility.Collapsed;
    }
}


public class NewPanelsTemplateVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((string)value == "Old Panels")
        {
            return Visibility.Collapsed;
        }
        return Visibility.Visible;
    }}

上面的解决方案是基于我的每个列表框都被具有特定 header 的组框包围(在其他情况下,您可能希望在转换器中使用列表框名称作为可见性切换) 感谢@PavelAnikhouski 提出转换器的想法。

我还尝试了@SamTheDev 提出的 DataTemplateSelector 解决方案并且它也有效(感谢@SamTheDev),但我更喜欢转换器解决方案,因为我对通过使用内容呈现器丢失数据上下文的想法感到不舒服(这里的底线是两种解决方案都有效,没有人更优雅,这只是个人喜好)