我不确定这是否是 DataGrid 控件或 MultiSelector 的问题,但当我在网格中选择多行时,每行都会触发 CollectionChanged 事件。如果我用鼠标“拖动”,这是有意义的,但如果我“按住 Shift 键并单击”选择多行或简单地单击左上角的“选择所有行”按钮,也会发生这种情况。
我在 MultiSelector 上看到有 Begin/EndUpdateSelectedItems 方法以及 IsUpdatingSelectedItems 属性。不幸的是,我的这个系列/活动的消费者不知道它的来源。
有没有办法让DataGrid / SelectedItems集合只在更新完成时发送CollectionChanged通知?
谢谢您。
编辑: 我发现对于 DataGrid,即使更改较大的选择,也不会设置 IsUpdatingSelectedItems 属性。
编辑: 我发现 DataGrid SelectionChanged 事件在完全更改后仅正确触发一次。不幸的是,因为这破坏了简单数据绑定的可能性,但如果您可以控制 SelectedItems 集合的使用者,那么这是一个潜在的解决方法。
为了完整起见,我将“回答”我自己的问题。事实证明,WPF 控件通常只能处理其 CollectionChanged 事件处理程序中的单个元素更改,这意味着“为每个项目调用 CollectionChanged”工作流程对于当前形式的框架来说是“正确”的方式。然而,我个人觉得这是一个可怕的性能问题。
private MultiSelector _selectedItems;
Public MultiSelector SelectedItems
{
get {return _selectedItems;
set { _selectedItems=value;}
}
将SelectedItems
属性绑定到 DataGrid 的 SelectedItem 并添加 System.Windows.Controls.Primitives.MultiSelector
using Microsoft.Xaml.Behaviors;
using System.Collections;
using System.Collections.Specialized;
using System.Windows;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class MultiSelectMultiSelectorBehavior : Behavior<System.Windows.Controls.Primitives.MultiSelector>
{
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(nameof(SelectedItems), typeof(INotifyCollectionChanged), typeof(MultiSelectMultiSelectorBehavior), new PropertyMetadata(OnSelectedItemsPropertyChanged));
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
SubscribeBinding(target, args.OldValue as INotifyCollectionChanged, false);
SubscribeBinding(target, args.NewValue as INotifyCollectionChanged, true);
}
private static void SubscribeBinding(DependencyObject target, INotifyCollectionChanged collection, bool subscribe)
{
if (collection == null) return;
if (subscribe)
{
collection.CollectionChanged += ((MultiSelectMultiSelectorBehavior)target).ContextSelectedItems_CollectionChanged;
}
else
{
collection.CollectionChanged -= ((MultiSelectMultiSelectorBehavior)target).ContextSelectedItems_CollectionChanged;
}
}
private void SubscribeGridChanged(bool subscribe)
{
if (subscribe)
{
AssociatedObject.AddHandler(System.Windows.Controls.Primitives.Selector.SelectionChangedEvent, new RoutedEventHandler(Grid_SelectionChangedEvent));
}
else
{
AssociatedObject.RemoveHandler(System.Windows.Controls.Primitives.Selector.SelectionChangedEvent, new RoutedEventHandler(Grid_SelectionChangedEvent));
}
}
protected override void OnAttached()
{
base.OnAttached();
SubscribeGridChanged(true);
}
protected override void OnDetaching()
{
base.OnDetaching();
SubscribeGridChanged(false);
}
private void SubscribeToEvents()
{
SubscribeGridChanged(true);
SubscribeBinding(this, SelectedItems, true);
}
private void UnsubscribeFromEvents()
{
SubscribeGridChanged(false);
SubscribeBinding(this, SelectedItems, false);
}
private void ContextSelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);
}
private void Grid_SelectionChangedEvent(object sender, RoutedEventArgs e)
{
Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);
}
public void Transfer(IList source, IList target)
{
if (source == null || target == null)
return;
UnsubscribeFromEvents();
(target as IBulkChange)?.BeginBulkOperation();
target.Clear();
foreach (var o in source)
{
target.Add(o);
}
(target as IBulkChange)?.EndBulkOperation();
SubscribeToEvents();
}
}
public interface IBulkChange
{
void BeginBulkOperation();
void EndBulkOperation();
}
[DebuggerDisplay("Count = {Count}")]
public class BulkObservableCollection<T> : ObservableCollection<T>, IBulkChange
{
private bool _collectionChangedDuringRangeOperation;
private int _rangeOperationCount;
private ReadOnlyObservableCollection<T> _readOnlyAccessor;
public BulkObservableCollection()
{
}
public BulkObservableCollection(List<T> list)
: base(list)
{
}
public BulkObservableCollection(IEnumerable<T> collection)
: base(collection)
{
}
public void SetCollection(IEnumerable<T> collection)
{
try
{
BeginBulkOperation();
Clear();
AddRange(collection);
}
finally
{
EndBulkOperation();
}
}
public void AddRange(IEnumerable<T> items)
{
if (items != null)
{
try
{
BeginBulkOperation();
foreach (T local in items)
{
Add(local);
}
}
finally
{
EndBulkOperation();
}
}
}
public void RemoveRange(IEnumerable<T> items)
{
if (items != null)
{
try
{
this.BeginBulkOperation();
foreach (T local in items)
{
base.Remove(local);
}
}
finally
{
this.EndBulkOperation();
}
}
}
public ReadOnlyObservableCollection<T> AsReadOnly()
{
if (this._readOnlyAccessor == null)
{
this._readOnlyAccessor = new ReadOnlyObservableCollection<T>(this);
}
return this._readOnlyAccessor;
}
public void BeginBulkOperation()
{
this._rangeOperationCount++;
}
public void EndBulkOperation()
{
if (((this._rangeOperationCount > 0) && (--this._rangeOperationCount == 0)) && this._collectionChangedDuringRangeOperation)
{
InternalEndBulkOperation();
}
}
private void InternalEndBulkOperation()
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
this._collectionChangedDuringRangeOperation = false;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (this._rangeOperationCount == 0)
{
base.OnCollectionChanged(e);
}
else
{
this._collectionChangedDuringRangeOperation = true;
}
}
public List<T> ToList()
{
return new List<T>(this);
}
}
然后是 XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
>
<DataGrid ItemsSource="{Binding AllItems}">
<b:Interaction.Behaviors>
<local:MultiSelectMultiSelectorBehavior SelectedItems="{Binding Path=SelectedItems}" />
</b:Interaction.Behaviors>
</DataGrid>
</Window>