我正在使用SelectionChanged
的{{1}}事件,但它"无法正常工作"。
这是repro:
ListBox
和xaml:
public partial class MainWindow : Window
{
readonly List<Item> _items = new List<Item>
{
new Item(),
... // add 20 more, to have selected item outside of visible region
new Item(),
new Item { IsSelected = true },
new Item(),
};
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
Debug.WriteLine($"Changed {e.AddedItems.Count}/{e.RemovedItems.Count}");
void button_Click(object sender, RoutedEventArgs e) =>
listBox.ItemsSource = listBox.ItemsSource == null ? _items : null;
}
public class Item
{
public bool IsSelected { get; set; }
}
添加到列表:
<Grid>
<ListBox x:Name="listBox" SelectionChanged="listBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Test" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="10" Click="button_Click" />
</Grid>
单击按钮将生成输出。凉。
删除该行,默认情况下VirtualizingPanel.IsVirtualizing="False"
将使用&#34;标准&#34;虚拟化:
未触发事件。我需要滚动到所选项目以触发事件。
将虚拟化更改为:
ListBox
WTF?即使滚动也不会触发事件。
问题:如何让VirtualizingPanel.VirtualizationMode="Recycling"
事件在最佳效果模式下正常 模式?
答案 0 :(得分:2)
使用虚拟化,如果某个项目没有与之关联的容器(ListBoxItem
),那么就没有应用ItemContainerStyle
的容器。这意味着在项目滚动到视图中之前,不会应用IsSelected
绑定。在设置该属性之前,不会发生选择更改,并且不会引发SelectionChanged
。
如何让SelectionChanged事件在最高性能模式下正常工作而无需像“标准”模式那样滚动?
可以说 *是* 正常工作。如果从MVVM角度处理此问题,则无需依赖UI元素中的事件。在您的模型中自己跟踪项目选择。您可以使用这样的实用程序类:
public interface ISelectable
{
bool IsSelected { get; set; }
}
public class ItemEventArgs<T> : EventArgs
{
public T Item { get; }
public ItemEventArgs(T item) => this.Item = item;
}
public class SelectionTracker<T> where T : ISelectable
{
private readonly ObservableCollection<T> _items;
private readonly ObservableCollection<T> _selectedItems;
private readonly ReadOnlyObservableCollection<T> _selectedItemsView;
private readonly HashSet<T> _trackedItems;
private readonly HashSet<T> _fastSelectedItems;
public SelectionTracker(ObservableCollection<T> items)
{
_items = items;
_selectedItems = new ObservableCollection<T>();
_selectedItemsView = new ReadOnlyObservableCollection<T>(_selectedItems);
_trackedItems = new HashSet<T>();
_fastSelectedItems = new HashSet<T>();
_items.CollectionChanged += OnCollectionChanged;
}
public event EventHandler<ItemEventArgs<T>> ItemSelected;
public event EventHandler<ItemEventArgs<T>> ItemUnselected;
public ReadOnlyObservableCollection<T> SelectedItems => _selectedItemsView;
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewItems == null)
goto default;
AddItems(e.NewItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems == null)
goto default;
RemoveItems(e.OldItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldItems == null || e.NewItems == null)
goto default;
RemoveItems(e.OldItems.OfType<T>());
AddItems(e.NewItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Move:
break;
default:
Refresh();
break;
}
}
public void Refresh()
{
RemoveItems(_trackedItems);
AddItems(_items);
}
private void AddItems(IEnumerable<T> items)
{
foreach (var item in items)
{
var observableItem = item as INotifyPropertyChanged;
if (observableItem != null)
observableItem.PropertyChanged += OnItemPropertyChanged;
_trackedItems.Add(item);
UpdateItem(item);
}
}
private void RemoveItems(IEnumerable<T> items)
{
foreach (var item in items)
{
var observableItem = item as INotifyPropertyChanged;
if (observableItem != null)
observableItem.PropertyChanged -= OnItemPropertyChanged;
_trackedItems.Remove(item);
UpdateItem(item);
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is T item)
UpdateItem(item);
}
private void UpdateItem(T item)
{
if (item?.IsSelected == true && _trackedItems.Contains(item))
{
if (_fastSelectedItems.Add(item))
{
_selectedItems.Add(item);
this.ItemSelected?.Invoke(this, new ItemEventArgs<T>(item));
}
}
else
{
if (_fastSelectedItems.Remove(item))
{
_selectedItems.Remove(item);
this.ItemUnselected?.Invoke(this, new ItemEventArgs<T>(item));
}
}
}
}
创建ObservableCollection
个项目时,请为该集合实例化SelectionTracker
。然后订阅ItemSelected
和ItemUnselected
以处理各个选择更改,或者订阅SelectedItems.CollectionChanged
。如果您不关心能够作为集合访问SelectedItems
,那么您可以摆脱_selectedItems
和_selectedItemsView
并避免一些列表删除开销。
嗯,这是一个奇怪的。我认为没有理由说这不应该在这种情况下起作用,但我也许可以理解为什么它总是不起作用。理论上,只要容器被“回收”并且其[有
VirtualizationMode="Recycling"
] WTF?即使滚动也不会触发事件。
DataContext
被分配了一个新项目,IsSelected
绑定就会更新。如果容器的先前分配的项目已选择 ,则可能不会触发属性更改,因此事件可能不会触发。但在您的示例中似乎并非如此。可能是如何实施回收的错误或意外后果。
IsSelected
来管理选择。我认为最重要的是使用ListBoxItem.IsSelected
来* 设置 *,选择是不可靠的;只应信任反映是否选择了给定的容器。它真正用于样式和模板触发器,以便他们可以知道是否将容器呈现为选定状态。从来没有打算管理选择,并且以这种方式使用它是错误的,因为它代表容器的选择状态而不是其关联的数据项。因此,它只适用于最天真,性能最差的场景,其中每个项目始终与其自己的容器相关联(无虚拟化)。