WPF DataGrid(MultiSelector?)多次引发 SelectedItems CollectionChanged 事件

发布于 2024-10-31 01:40:46 字数 612 浏览 5 评论 0原文

我不确定这是否是 DataGrid 控件或 MultiSelector 的问题,但当我在网格中选择多行时,每行都会触发 CollectionChanged 事件。如果我用鼠标“拖动”,这是有意义的,但如果我“按住 Shift 键并单击”选择多行或简单地单击左上角的“选择所有行”按钮,也会发生这种情况。

我在 MultiSelector 上看到有 Begin/EndUpdateSelectedItems 方法以及 IsUpdatingSelectedItems 属性。不幸的是,我的这个集合/事件的消费者不知道它的来源。

有没有办法让 DataGrid / SelectedItems 集合仅在更新完成时发送 CollectionChanged 通知?

谢谢你。

编辑: 我发现对于 DataGrid,即使更改较大的选择,也不会设置 IsUpdatingSelectedItems 属性。

编辑: 我发现 DataGrid SelectionChanged 事件在完全更改后仅正确触发一次。不幸的是,这破坏了简单数据绑定的可能性,但如果您可以控制 SelectedItems 集合的使用者,那么它是一个潜在的解决方法。

I'm not sure if this is an issue with the DataGrid control or with MultiSelectors in general, but when I select multiple rows within the grid, the CollectionChanged event is fired for every single row. This makes sense if I'm 'dragging' with my mouse, but it also occurs if I 'shift-click' to select multiple rows or simply click the top-left 'select-all-rows' button.

I have seen on the MultiSelector that there are Begin/EndUpdateSelectedItems methods as well as an IsUpdatingSelectedItems property. Unfortunately my consumer of this collection/event is unaware of its source.

Is there a way to make the DataGrid / SelectedItems collection only send the CollectionChanged notification when updating is finished?

thank you kindly.

Edit:
I have found that for the DataGrid the IsUpdatingSelectedItems property is not being set even when changing a large selection.

Edit:
I have found that the DataGrid SelectionChanged event is correctly fired only once after the full change. It's unfortunate since this ruins the possibility of simple data binding, but it is a potential workaround if you have control over the consumer of the SelectedItems collection.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

雪落纷纷 2024-11-07 01:40:46

为了完整起见,我将“回答”我自己的问题。事实证明,WPF 控件通常只能处理其 CollectionChanged 事件处理程序中的单个元素更改之外的任何内容 - 这意味着“为每个项目调用 CollectionChanged”工作流程对于框架来说是正确的方式。目前的形式。然而,我个人认为这是一个可怕的性能问题。

For the sake of completeness, I'll 'answer' my own question. It turns out that the WPF controls in general can not handle anything but a single element change in their CollectionChanged event handlers - meaning that the 'call CollectionChanged for every item' workflow is the right way for the framework in its current form. However, personally I feel that this is a terrible performance issue.

疏忽 2024-11-07 01:40:46

ViewModel:

private MultiSelector _selectedItems;

  Public  MultiSelector SelectedItems
    {
        get {return _selectedItems;
        set { _selectedItems=value;}                             
      } 

SelectedItems属性绑定到DataGrid的SelectedItem,并添加System.Windows.Controls.Primitives.MultiSelector

ViewModel:

private MultiSelector _selectedItems;

  Public  MultiSelector SelectedItems
    {
        get {return _selectedItems;
        set { _selectedItems=value;}                             
      } 

Bind the SelectedItems Property to the SelectedItem of the DataGrid and add System.Windows.Controls.Primitives.MultiSelector

居里长安 2024-11-07 01:40:46

我还发现这非常烦人,当我一次选择多行时,它会为所选的每个项目发送更改事件。
所以我做了一个行为......以及一个 BulkObservableCollection 来处理它,以便它只发送一个事件而不是所有事件。
下面的代码是在 XAML 中使用它的快速示例

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);
    }
}

<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>

I also found this very annoying that when I was selecting multiple lines at once it would send a changed event for every single item selected.
So I have a behaviour that I made... And a BulkObservableCollection which will handle it so that it only sends one event rather than all of them.
The code followed by a quick example of using it in XAML is:

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);
    }
}

Then the 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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文