WPF DataGrid(多线程?)正在多次引发SelectedItems CollectionChanged事件

gpnt7bae  于 2023-10-22  发布在  其他
关注(0)|答案(3)|浏览(173)

我不确定这是DataGrid控件的问题还是MultiSelectors的问题,但是当我在网格中选择多行时,CollectionChanged事件会为每一行触发。如果我用鼠标拖动,这是有意义的,但如果我'shift-click'选择多行或简单地单击左上角的'select-all-rows'按钮,它也会发生。
我在Multiplexer上看到有开始/EndUpdateSelectedItems方法以及IsUpdatingSelectedItems属性。不幸的是,我的这个收藏/事件的消费者不知道它的来源。
有没有一种方法可以让DataGrid / SelectedItems集合只在更新完成时发送CollectionChanged通知?
非常感谢。

**编辑:**我发现DataGrid的IsUpdatingSelectedItems属性没有设置,即使在更改大的选择时也是如此。
**编辑:**我发现DataGrid SelectionChanged事件在完全更改后仅正确触发一次。这是不幸的,因为这破坏了简单数据绑定的可能性,但如果您可以控制SelectedItems集合的使用者,这是一种潜在的解决方法。

slwdgvem

slwdgvem1#

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

dxxyhpgq

dxxyhpgq2#

ViewModel:

private MultiSelector _selectedItems;

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

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

cotxawn7

cotxawn73#

我还发现,当我一次选择多行时,它会为每一个选中的项目发送一个更改事件,这非常烦人。所以我做了一个行为...和一个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);
    }
}

然后是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>

相关问题