问题描述
在我的程序中,我有一组视图模型对象来表示 ListBox 中的项目(允许多选).viewmodel 有一个 IsSelected 属性,我想将其绑定到 ListBox,以便在 viewmodel 中而不是在列表框本身中管理选择状态.
In my program I have a set of view-model objects to represent items in a ListBox (multi-select is allowed). The viewmodel has an IsSelected property that I would like to bind to the ListBox so that selection state is managed in the viewmodel rather than in the listbox itself.
但是,显然 ListBox 不维护大多数屏幕外项目的绑定,因此通常 IsSelected 属性未正确同步.这是一些演示问题的代码.第一个 XAML:
However, apparently the ListBox doesn't maintain bindings for most of the off-screen items, so in general the IsSelected property is not synchronized correctly. Here is some code that demonstrates the problem. First 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>
C# 全选处理程序:
private void TestSelectAll_Click(object sender, RoutedEventArgs e)
{
foreach (var item in _dataContext.Items)
item.IsSelected = true;
}
C# 视图模型:
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));
}
}
可以观察到两个不同的问题.
Two separate problems can be observed.
- 如果单击第一个项目,然后按 Shift+End,则应选择所有 200 个项目;但是,标题报告只选择了 21 个项目.
- 如果您单击全选",则确实选择了所有项目.如果您然后单击 ListBox 中的一个项目,您会希望取消选择其他 199 个项目,但这不会发生.相反,只有屏幕上的项目(以及其他一些项目)被取消选择.除非您首先从头到尾滚动列表,否则所有 199 个项目都不会被取消选择(即使这样,奇怪的是,如果您使用小滚动框执行滚动,它也不起作用).
我的问题是:
- 有人能准确解释为什么会这样吗?
- 我可以避免或解决这个问题吗?
推荐答案
ListBox
默认情况下是 UI 虚拟化的.这意味着在任何给定时刻,只有 ItemsSource
中的可见项目(以及几乎可见"项目的一小部分)将实际呈现.这就解释了为什么更新 source 可以按预期工作(因为这些项目始终存在),但只是导航 UI 却不能(因为这些项目的视觉表示是动态创建和销毁的,并且永远不会同时存在.)
ListBox
is, by default, UI virtualized. That means that at any given moment, only the visible items (along with a small subset of "almost visible" items) in the ItemsSource
will actually be rendered. That explains why updating the source works as expected (since those items always exist,) but just navigating the UI doesn't (since the visual representations of those items are created and destroyed on the fly, and never exist together at once.)
如果您想关闭此行为,一种选择是在您的 ListBox
上设置 ScrollViewer.CanContentScroll=False
.这将启用平滑"滚动,并隐式关闭虚拟化.要显式禁用虚拟化,您可以设置 VirtualizingStackPanel.IsVirtualizing=False
.
If you want to turn off this behaviour, one option is to set ScrollViewer.CanContentScroll=False
on your ListBox
. This will enable "smooth" scrolling, and implicitly turn off virtualization. To disable virtualization explicitly, you can set VirtualizingStackPanel.IsVirtualizing=False
.
这篇关于WPF:绑定到 ListBoxItem.IsSelected 不适用于屏幕外项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!