为什么 DataTrigger 只适用于列表框中的最后一个元素?

问题描述 投票:0回答:1

我试图找到这个问题的答案(列表框项目根据视图模型属性显示复选框或单选按钮),我遇到了一个非常奇怪的行为。

如果我使用

DataTrigger
设置列表框中某个项目的
ContentControl
DataTemplate
的内容,则只有最后一项似乎可以正常工作。

为什么只有最后一项有复选框/单选按钮?

这是 XAML,其中窗口视图模型属性

AllowMultiItem
定义我是否需要复选框或单选按钮:

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

当我这样做时,无论

AllowMultiItem
为真或假,只有列表框中的最后一项收到复选框/单选按钮。

为什么会出现这种情况?我该如何解决?

其他代码 - 可能不相关

不确定它是否有用,但是如果您想检查窗口视图模型和项目视图模型,或者如果您想完全重现我的测试,代码如下。我尽可能简单地进行了此测试,因此没有不相关的错误影响结果。

窗口视图模型(带有静态打开窗口的函数):

// 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();
    }
}

项目视图模型:

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.ShowWindow(true)
MyViewModel.ShowWindow(false)

我尝试过的一些事情

  • 尝试将
    INotifyPropertyChanged
    添加到
    MyViewModel
    并在
    AllowMultiItem
    之后将更改触发到
    OnContentRendered
    - 没有任何变化
  • 尝试将
    DataTrigger
    绑定更改为针对窗口的
    RelativeSource
    :奇怪的是,现在first项目获得了盒子,但只有第一个
c# wpf listbox datatrigger
1个回答
0
投票

因为在 Content 中您编写了 RadioButton 或 CheckBox 元素的实例。并且一个实例只能显示在一个视觉位置(框架)。

这是正确的实现,能够切换到运行时。

// This is the namespace for my implementation of the ViewModelBase class.
// Since you will have your own implementation or use some kind of package,
// you need to change this `using` to the one that is relevant for you.
using Simplified;

using System.Collections.ObjectModel;

namespace Core2024.SO.Daniel_Möller.question78429551
{
    public class MyViewModel : ViewModelBase
    {
        public ObservableCollection<MyListItem> Items { get; }
        public bool AllowMultiItem { get => Get<bool>(); set => Set(value); }

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

        public MyViewModel()
            : this(Enumerable.Range(1, 10).Select(index => new MyListItem("Item " + index.ToString())),
                   true)
        { }
    }

    public class MyListItem : ViewModelBase
    {

        public string Text { get => Get<string>(); set => Set(value); }
        public bool IsChecked { get => Get<bool>(); set => Set(value); }

        public MyListItem(string text)
        {
            Text = text;
        }
    }
}
<Window x:Class="Core2024.SO.Daniel_Möller.question78429551.TemplateSwitcherWindow"
        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:Core2024.SO.Daniel_Möller.question78429551"
        mc:Ignorable="d"
        Title="TemplateSwitcherWindow" Height="450" Width="800"
        DataContext="{DynamicResource vm}">
    <Window.Resources>
        <local:MyViewModel x:Key="vm"/>
        <DataTemplate x:Key="ListItem.Temlate.RadioButton"
                      DataType="{x:Type local:MyListItem}">
            <RadioButton IsChecked="{Binding IsChecked}" GroupName="RadioGroup"
                         Content="{Binding Text}"/>
        </DataTemplate>
        <DataTemplate x:Key="ListItem.Temlate.CheckBox"
                      DataType="{x:Type local:MyListItem}">
            <CheckBox IsChecked="{Binding IsChecked}"
                         Content="{Binding Text}"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <CheckBox Content="AllowMultiItem" IsChecked="{Binding AllowMultiItem}"/>

        <ListBox Grid.Row="1" ItemsSource="{Binding Items}">
            <ListBox.Style>
                <Style TargetType="ListBox">
                    <Setter Property="ItemTemplate" Value="{DynamicResource ListItem.Temlate.RadioButton}"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding AllowMultiItem}"
                                     Value="True">
                            <Setter Property="ItemTemplate" Value="{DynamicResource ListItem.Temlate.CheckBox}"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ListBox.Style>
        </ListBox>
    </Grid>
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.