扩展选择模式,虚拟化和IsSelected绑定

9
在扩展选择模式下,似乎IsSelected绑定存在问题。看起来只有最后一个选中的项目会被正确处理,而其他超出范围的项目则不会。
演示:

项目 012989796 被使用 Control 选择。当选择 94(不使用 Control!)时,选择计数器应该为 1,但实际上看到的是 3。向上滚动会发现,超出范围的最后一个选定项被取消了选择。

以下是 mcve:

xaml:

<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended" SelectionChanged="ListBox_SelectionChanged">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Text}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

cs:

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string property = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}

public class Item : NotifyPropertyChanged
{
    bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set { _isSelected = value; }
    }

    public string Text { get; set; }
}

public class ViewModel : NotifyPropertyChanged
{
    public ObservableCollection<Item> Items { get; }

    public ViewModel()
    {
        var list = new List<Item>();
        for (int i = 0; i < 100; i++)
            list.Add(new Item() { Text = i.ToString() });
        Items = new ObservableCollection<Item>(list);
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }

    void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Title = ((ViewModel)DataContext).Items.Count(item => item.IsSelected).ToString();
    }
}

一个快速的解决方法是禁用列表控件(ListBoxListView)的虚拟化:

VirtualizingStackPanel.IsVirtualizing="False"

问题:有什么办法可以在不禁用虚拟化的情况下解决它吗?


当启用虚拟化时,我怀疑"selected"选定项目方面是需要自己处理的。就本机Windows而言,虚拟化只提供有关应该显示哪些项目的提示,而不是为您显示它们。它与滚动条协同工作。这使得您的应用程序可以显示数千个项目。可见项目数通常保持不变(除了在最后一页或项目数少于屏幕上正常显示数量时)。 - user585968
我觉得可视化模式设置为循环,所以它会重复使用选定的项目。你尝试过更改虚拟化模式了吗?例如:VirtualizingPanel.VirtualizationMode="Standard" - XAMlMAX
@XAMlMAX,你可以复制mcve并尝试自己解决问题。VirtualizingMode="Standard"不会改善任何东西(已尝试使用VirtualizingPanelVirtualizingStackPanel)。 - Sinatr
重复的内容 VirtualizingStackPanel + MVVM + 多选 - Herohtar
1个回答

11

好的,这是预期行为。虚拟化只为可见项创建视觉容器(ListBoxItem)。为了使绑定工作,容器必须首先存在,因此只有可见项受到影响。

有两个明显的解决方案:

  1. 禁用虚拟化。
  2. 改为使用SelectionChanged事件,您可以从SelectionChangedEventArgs中获取添加和移除的项。然后,您只需要执行类型转换并相应地设置IsSelected属性(您不需要迭代Items)。Ctrl+A也可以工作,您只需要处理添加的项即可(并完全删除绑定):

    void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        foreach (var removedItem in e.RemovedItems.Cast<Item>())
        {
            removedItem.IsSelected = false;
        }
        foreach (var addedItem in e.AddedItems.Cast<Item>())
        {
            addedItem.IsSelected = true;
        }
        Title = ((ViewModel) DataContext).Items.Count(item => item.IsSelected).ToString();
    }
    

1
取消选择最后一个选项是否超出范围也是预期行为吗?我无法解释,你能吗? - Sinatr
@Sinatr,预计它无法正常工作。我不知道为什么它不能以这种特定的方式工作,我只能猜测。 :) 这可能与ListViewItems何时以及如何清除有关。如果我要猜测,我会说最后选择的项容器在某种程度上被“保留”了SelectedItem属性,因此当您从其导航离开时它不会被销毁(与当前选择中的所有其他项不同)。 - Nikita B
我明白了。一开始我忽略了你使用添加/删除项目的建议,这很有用。目前正在尝试使其适用于CTR+A,而不会出现添加项目时的StackOverflowException - Sinatr
@Sinatr,我发布的代码在我的机器上运行没有异常,我不知道是什么原因导致堆栈溢出,因为没有递归。请确保从你的XAML中删除“IsSelected”绑定。 - Nikita B
我不想移除绑定(ViewModel需要它来恢复选择)。而且你的解决方案比我的更好(移除它)。 - Sinatr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接