WPF:绑定到ListBoxItem.IsSelected不适用于屏幕外项目

Qwe*_*tie 8 .net wpf binding listbox mvvm

在我的程序中,我有一组视图模型对象来表示ListBox中的项目(允许多选).viewmodel有一个IsSelected属性,我想绑定到ListBox,以便在viewmodel而不是列表框本身中管理选择状态.

但是,显然ListBox不会保留大多数屏幕外项目的绑定,因此通常IsSelected属性未正确同步.这是一些演示此问题的代码.第一个XAML:

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBlock>Number of selected items: </TextBlock>
        <TextBlock Text="{Binding NumItemsSelected}"/>
    </StackPanel>
    <ListBox ItemsSource="{Binding Items}" Height="200" SelectionMode="Extended">
        <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
    <Button Name="TestSelectAll" Click="TestSelectAll_Click">Select all</Button>
</StackPanel>
Run Code Online (Sandbox Code Playgroud)

C#Select All处理程序:

private void TestSelectAll_Click(object sender, RoutedEventArgs e)
{
    foreach (var item in _dataContext.Items)
        item.IsSelected = true;
}
Run Code Online (Sandbox Code Playgroud)

C#viewmodel:

public class TestItem : NPCHelper
{
    TestDataContext _c;
    string _text;
    public TestItem(TestDataContext c, string text) { _c = c; _text = text; }

    public override string ToString() { return _text; }

    bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set {
            _isSelected = value; 
            FirePropertyChanged("IsSelected");
            _c.FirePropertyChanged("NumItemsSelected");
        }
    }
}
public class TestDataContext : NPCHelper
{
    public TestDataContext()
    {
        for (int i = 0; i < 200; i++)
            _items.Add(new TestItem(this, i.ToString()));
    }
    ObservableCollection<TestItem> _items = new ObservableCollection<TestItem>();
    public ObservableCollection<TestItem> Items { get { return _items; } }

    public int NumItemsSelected { get { return _items.Where(it => it.IsSelected).Count(); } }
}
public class NPCHelper : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void FirePropertyChanged(string prop)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
    }
}
Run Code Online (Sandbox Code Playgroud)

可以观察到两个独立的问题.

  1. 如果单击第一个项目然后按Shift + End,则应选择所有200个项目; 但是,标题报告只选择了21个项目.
  2. 如果单击"全选",则确实选中了所有项目.如果您随后单击ListBox中的某个项目,则可能会取消选择其他199个项目,但这不会发生.相反,仅取消选择屏幕上的项目(以及其他一些项目).除非您首先从头到尾滚动列表,否则不会取消选择所有199个项目(即使这样,奇怪的是,如果您使用小滚动框滚动,它也不起作用).

我的问题是:

  1. 有人能解释为什么会发生这种情况吗
  2. 我可以避免或解决这个问题吗?

dle*_*lev 11

ListBox默认情况下,UI是虚拟化的.这意味着在任何给定时刻,ItemsSource实际上只会呈现遗嘱中的可见项目(以及"几乎可见"项目的一小部分).这解释了为什么更新按预期工作(因为这些项始终存在),但只是导航UI不会(因为这些项的可视化表示是动态创建和销毁的,并且不会同时存在.)

如果要关闭此行为,一个选项是设置ScrollViewer.CanContentScroll=False您的ListBox.这将实现"平滑"滚动,并隐式关闭虚拟化.要明确禁用虚拟化,您可以进行设置VirtualizingStackPanel.IsVirtualizing=False.

  • 哇,这对于表演很糟糕.如果我在测试列表中放置10000个项目,显示列表似乎永远需要,Select All需要永远,并且内存使用膨胀将近80 MB根据任务管理器(这意味着WPF每个项目需要8KB,并记住,这是为了没有DataTemplate的"琐碎"项目!微软,你有时会让我感到厌恶!).我的应用程序只需要大约1000个项目,但是,这仍然是为MVVM支付的高昂代价. (2认同)