You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF中DataTrigger仅对ListBox最后一项生效的原因及解决办法

问题:WPF ListBox仅最后一项显示CheckBox/RadioButton

在实现ListBox项根据ViewModel的AllowMultiItem属性动态显示CheckBox或RadioButton时,遇到异常:无论AllowMultiItem为true还是false,只有ListBox的最后一项能正常显示对应的控件,其余项均不显示。

相关XAML代码如下:

<Window x:Class="TestSpace.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestSpace"
        mc:Ignorable="d"
        Title="MyWindow" Height="450" Width="800">
  <Grid>

    <!-- the list box -->
    <ListBox x:Name="MyList" ItemsSource="{Binding Items}">
      <ListBox.ItemTemplate>
        <DataTemplate DataType="{x:Type local:MyListItem}">
          <StackPanel Orientation="Horizontal">

            <!-- this content control selects between check and radio-->
            <ContentControl>
              <ContentControl.Style>
                <Style TargetType="ContentControl">
                  <Style.Triggers>

                    <!-- trigger that creates checkbox -->
                    <DataTrigger Value="True" 
                                 Binding="{Binding Path=DataContext.AllowMultiItem, ElementName=MyList}">
                      <Setter Property="Content">
                        <Setter.Value>
                          <CheckBox IsChecked="{Binding IsChecked}"/>
                        </Setter.Value>
                      </Setter>
                    </DataTrigger>

                    <!-- trigger that creates radio button -->
                    <DataTrigger Value="False" 
                                 Binding="{Binding Path=DataContext.AllowMultiItem, ElementName=MyList}">
                      <Setter Property="Content">
                        <Setter.Value>
                          <RadioButton IsChecked="{Binding IsChecked}" GroupName="RadioGroup"/>
                        </Setter.Value>
                      </Setter>
                    </DataTrigger>


                  </Style.Triggers>
                </Style>
              </ContentControl.Style>
            </ContentControl>
                        
            <!-- item text -->
            <TextBlock Text="{Binding Text}"/>

          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
  </Grid>
</Window>

补充的ViewModel代码:
窗口ViewModel:

// didn't feel the need for INotifyPropertyChanged 
// since nothing here is supposed to change
public class MyViewModel 
{
    public List<MyListItem> Items { get; private set; }
    public bool AllowMultiItem { get; private set; }

    public MyViewModel(List<MyListItem> items, bool allowMultiItem)
    {
        Items = items;
        AllowMultiItem = allowMultiItem;
    }

    public static void ShowWindow(bool allowMultiItem)
    {
        // just creating items with names from 1 to 10
        List<MyListItem> items = Enumerable.Range(1, 10)
            .Select(index=> new MyListItem("Item " + index.ToString()))
            .ToList();

        // create view model
        MyViewModel vm = new MyViewModel(items, allowMultiItem);

        // create and show window
        MyWindow window = new MyWindow();
        window.DataContext = vm;
        window.ShowDialog();
    }
}

项ViewModel:

public class MyListItem : INotifyPropertyChanged
{
    private bool _checked;
    private string _text;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Text { get { return _text; } set { SetText(value); } }
    public bool IsChecked { get { return _checked; } set { SetChecked(value); } }

    public MyListItem(string text)
    {
        SetText(text);
        SetChecked(false);
    }

    private void SetChecked(bool value)
    {
        if (value == _checked) return;

        _checked = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
    }

    private void SetText(string value)
    {
        if (value == _text) 
            return;

        _text = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Text"));
    }
}

已尝试的方法:

  • 为MyViewModel添加INotifyPropertyChanged并在OnContentRendered后触发AllowMultiItem变更,无效果
  • 将DataTrigger绑定改为RelativeSource指向Window,此时仅第一项能显示控件,但仅第一项生效

原因分析

问题出在Style中的Setter直接创建UI元素的方式上:WPF的Style是共享资源,当你在Style的Setter.Value中直接定义CheckBox或RadioButton时,这些UI元素是单一实例。ListBox的ItemTemplate会复用这个Style,而一个UI元素只能属于一个VisualTree,所以最后只有最后一个ListBoxItem能成功将这个单一实例的控件加入自己的VisualTree,前面的项都会因为控件已经被占用而无法显示。


解决方案

有两种常用的解决方式:

方式一:使用DataTemplate作为Content的值

将CheckBox和RadioButton分别定义为DataTemplate,在Setter中设置ContentTemplate而非直接设置Content,这样每个项都会创建独立的控件实例:

<Window x:Class="TestSpace.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestSpace"
        mc:Ignorable="d"
        Title="MyWindow" Height="450" Width="800">
  <Window.Resources>
    <!-- 定义CheckBox的DataTemplate -->
    <DataTemplate x:Key="CheckBoxTemplate">
      <CheckBox IsChecked="{Binding IsChecked}"/>
    </DataTemplate>
    <!-- 定义RadioButton的DataTemplate -->
    <DataTemplate x:Key="RadioButtonTemplate">
      <RadioButton IsChecked="{Binding IsChecked}" GroupName="RadioGroup"/>
    </DataTemplate>
  </Window.Resources>
  <Grid>
    <ListBox x:Name="MyList" ItemsSource="{Binding Items}">
      <ListBox.ItemTemplate>
        <DataTemplate DataType="{x:Type local:MyListItem}">
          <StackPanel Orientation="Horizontal">
            <ContentControl>
              <ContentControl.Style>
                <Style TargetType="ContentControl">
                  <Style.Triggers>
                    <DataTrigger Value="True" 
                                 Binding="{Binding Path=DataContext.AllowMultiItem, ElementName=MyList}">
                      <Setter Property="ContentTemplate" Value="{StaticResource CheckBoxTemplate}"/>
                    </DataTrigger>
                    <DataTrigger Value="False" 
                                 Binding="{Binding Path=DataContext.AllowMultiItem, ElementName=MyList}">
                      <Setter Property="ContentTemplate" Value="{StaticResource RadioButtonTemplate}"/>
                    </DataTrigger>
                  </Style.Triggers>
                </Style>
              </ContentControl.Style>
              <!-- 设置Content为当前项的ViewModel,让DataTemplate绑定生效 -->
              <ContentControl.Content>
                <Binding />
              </ContentControl.Content>
            </ContentControl>
            <TextBlock Text="{Binding Text}"/>
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
  </Grid>
</Window>

方式二:使用DataTemplateSelector

自定义DataTemplateSelector,根据AllowMultiItem属性选择对应的模板:

1. 创建TemplateSelector类

public class SelectionTemplateSelector : DataTemplateSelector
{
    public DataTemplate CheckBoxTemplate { get; set; }
    public DataTemplate RadioButtonTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        // 获取ListBox的DataContext(即MyViewModel)
        var listBox = FindAncestor<ListBox>(container);
        if (listBox?.DataContext is MyViewModel vm)
        {
            return vm.AllowMultiItem ? CheckBoxTemplate : RadioButtonTemplate;
        }
        return base.SelectTemplate(item, container);
    }

    // 查找父级元素的辅助方法
    private T FindAncestor<T>(DependencyObject current) where T : DependencyObject
    {
        do
        {
            if (current is T ancestor)
                return ancestor;
            current = VisualTreeHelper.GetParent(current);
        } while (current != null);
        return null;
    }
}

2. 在XAML中使用Selector

<Window x:Class="TestSpace.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestSpace"
        mc:Ignorable="d"
        Title="MyWindow" Height="450" Width="800">
  <Window.Resources>
    <DataTemplate x:Key="CheckBoxTemplate">
      <CheckBox IsChecked="{Binding IsChecked}"/>
    </DataTemplate>
    <DataTemplate x:Key="RadioButtonTemplate">
      <RadioButton IsChecked="{Binding IsChecked}" GroupName="RadioGroup"/>
    </DataTemplate>
    <local:SelectionTemplateSelector x:Key="SelectionTemplateSelector"
                                     CheckBoxTemplate="{StaticResource CheckBoxTemplate}"
                                     RadioButtonTemplate="{StaticResource RadioButtonTemplate}"/>
  </Window.Resources>
  <Grid>
    <ListBox x:Name="MyList" ItemsSource="{Binding Items}">
      <ListBox.ItemTemplate>
        <DataTemplate DataType="{x:Type local:MyListItem}">
          <StackPanel Orientation="Horizontal">
            <ContentControl ContentTemplateSelector="{StaticResource SelectionTemplateSelector}"
                            Content="{Binding}"/>
            <TextBlock Text="{Binding Text}"/>
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
  </Grid>
</Window>

内容的提问来源于stack exchange,提问作者Daniel Müller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 20:00:55