태그 보관물: observablecollection

observablecollection

ObservableCollection을 지울 때 e.OldItems에 항목이 없습니다. 처리기도 있습니다. 이 경우

나는 여기에 정말로 나를 방해하는 무언가가 있습니다.

항목으로 채워진 ObservableCollection T가 있습니다. CollectionChanged 이벤트에 연결된 이벤트 처리기도 있습니다.

이 경우 취소 컬렉션을이 NotifyCollectionChangedAction.Reset에 e.Action 설정과 CollectionChanged 이벤트가 발생합니다. 네, 정상입니다. 그러나 이상한 점은 e.OldItems 또는 e.NewItems에 아무것도 없다는 것입니다. e.OldItems가 컬렉션에서 제거 된 모든 항목으로 채워질 것으로 예상합니다.

다른 사람이 본 적이 있습니까? 만약 그렇다면, 그들은 어떻게 주변에 있었습니까?

몇 가지 배경 : CollectionChanged 이벤트를 사용하여 다른 이벤트에 연결 및 분리하므로 e.OldItems에 항목이 없으면 해당 이벤트에서 분리 할 수 ​​없습니다.

설명은 :
나는 문서하지 않는 것을 알고 크게 는 이런 식으로 행동해야한다는 상태. 그러나 다른 모든 작업에 대해 수행 한 작업을 알려줍니다. 따라서 내 가정은 Clear / Reset의 경우에도 나에게 말할 것입니다.

다음은 직접 재현하려는 경우 샘플 코드입니다. 먼저 xaml에서 :

<Window
    x:Class="ObservableCollection.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1"
    Height="300"
    Width="300"
>
    <StackPanel>
        <Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
        <Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
        <Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
        <Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
        <Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
    </StackPanel>
</Window>

다음으로 코드는 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace ObservableCollection
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            _integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
        }

        private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }

        private void addButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Add(25);
        }

        private void moveButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Move(0, 19);
        }

        private void removeButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.RemoveAt(0);
        }

        private void replaceButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection[0] = 50;
        }

        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Clear();
        }

        private ObservableCollection<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
    }
}


답변

재설정은 목록이 지워진 것을 의미하지 않기 때문에 이전 항목을 포함한다고 주장하지 않습니다.

이는 극적인 일이 발생했으며 추가 / 제거 작업 비용이 목록을 처음부터 다시 스캔하는 비용을 초과 할 가능성이 높으므로 그렇게해야합니다.

MSDN은 전체 컬렉션이 재설정 후보로 다시 정렬되는 예를 제안합니다.

반복합니다. 리셋 분명 의미하지 않는다 , 그것은 의미 목록에 대한 귀하의 가정이 이제 유효하지 않습니다. 완전히 새로운 목록 인 것처럼 취급하십시오 . 분명한 사례가 하나 있지만 다른 사례도있을 수 있습니다.

몇 가지 예 :
많은 항목이 포함 된 이와 같은 목록이 있고 ListView화면에 표시 하기 위해 WPF 에 데이터 바인딩되었습니다 .
목록을 지우고 .Reset이벤트를 발생 시키면 성능이 거의 즉시 발생하지만 대신 많은 개별 .Remove이벤트를 발생 시키면 WPF가 항목을 하나씩 제거하므로 성능이 끔찍합니다. 또한 .Reset수천 개의 개별 Move작업을 실행하는 대신 목록이 재정렬되었음을 나타 내기 위해 자체 코드를 사용 했습니다 . Clear와 마찬가지로 많은 개별 이벤트를 제기 할 때 큰 성능 타격이 있습니다.


답변

여기서도 같은 문제가 발생했습니다. CollectionChanged의 재설정 작업에는 OldItems가 포함되지 않습니다. 해결 방법이있었습니다. 대신 다음 확장 방법을 사용했습니다.

public static void RemoveAll(this IList list)
{
   while (list.Count > 0)
   {
      list.RemoveAt(list.Count - 1);
   }
}

결국 Clear () 함수를 지원하지 않고 Reset 작업에 대한 CollectionChanged 이벤트에서 NotSupportedException을 던졌습니다. RemoveAll은 적절한 OldItems와 함께 CollectionChanged 이벤트에서 Remove 작업을 트리거합니다.


답변

또 다른 옵션은 Reset 이벤트를 다음과 같이 OldItems 속성에 지워진 항목이 모두있는 단일 Remove 이벤트로 바꾸는 것입니다.

public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
    protected override void ClearItems()
    {
        List<T> removed = new List<T>(this);
        base.ClearItems();
        base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Reset)
            base.OnCollectionChanged(e);
    }
    // Constructors omitted
    ...
}

장점 :

  1. 추가 이벤트에 가입 할 필요 없음 (수락 된 답변에 따라 필요)

  2. 제거 된 각 개체에 대해 이벤트를 생성하지 않습니다 (다른 제안 된 솔루션으로 인해 여러 제거 된 이벤트가 발생 함).

  3. 구독자는 필요에 따라 이벤트 처리기를 추가 / 제거하기 위해 모든 이벤트에서 NewItems 및 OldItems 만 확인하면됩니다.

단점 :

  1. 재설정 이벤트 없음

  2. 목록 사본을 만드는 작은 (?) 오버 헤드.

  3. ???

2012-02-23 수정

불행히도 WPF 목록 기반 컨트롤에 바인딩 된 경우 여러 요소가있는 ObservableCollectionNoReset 컬렉션을 지우면 “범위 작업이 지원되지 않음”예외가 발생합니다. 이 제한이있는 컨트롤과 함께 사용하기 위해 ObservableCollectionNoReset 클래스를 다음과 같이 변경했습니다.

public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
    // Some CollectionChanged listeners don't support range actions.
    public Boolean RangeActionsSupported { get; set; }

    protected override void ClearItems()
    {
        if (RangeActionsSupported)
        {
            List<T> removed = new List<T>(this);
            base.ClearItems();
            base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
        }
        else
        {
            while (Count > 0 )
                base.RemoveAt(Count - 1);
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Reset)
            base.OnCollectionChanged(e);
    }

    public ObservableCollectionNoReset(Boolean rangeActionsSupported = false)
    {
        RangeActionsSupported = rangeActionsSupported;
    }

    // Additional constructors omitted.
 }

RangeActionsSupported가 false (기본값) 인 경우 컬렉션의 개체 당 하나의 Remove 알림이 생성되므로 이는 효율적이지 않습니다.


답변

좋아요, 이것은 매우 오래된 질문이라는 것을 알고 있지만 문제에 대한 좋은 해결책을 찾았고 공유 할 것이라고 생각했습니다. 이 솔루션은 여기에있는 많은 훌륭한 답변에서 영감을 얻었지만 다음과 같은 장점이 있습니다.

  • ObservableCollection에서 새 클래스를 만들고 메서드를 재정의 할 필요가 없습니다.
  • NotifyCollectionChanged의 작동을 변경하지 않습니다 (따라서 재설정을 엉망으로 만들지 않음)
  • 반사를 사용하지 않음

다음은 코드입니다.

 public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction)
 {
     unhookAction.Invoke(collection);
     collection.Clear();
 }

이 확장 메서드 Action는 컬렉션이 지워지기 전에 호출 될를받습니다.


답변

사용자가 하나의 이벤트 만 발생시키면서 한 번에 많은 항목을 추가하거나 제거하는 효율성을 모두 활용할 수있는 솔루션을 찾았으며 다른 모든 사용자는 Action.Reset 이벤트 인수를 가져 오는 UIElements의 요구 사항을 충족합니다. 추가 및 제거 된 요소 목록과 같습니다.

이 솔루션에는 CollectionChanged 이벤트 재정의가 포함됩니다. 이 이벤트를 시작하려면 실제로 등록 된 각 핸들러의 대상을보고 유형을 결정할 수 있습니다. NotifyCollectionChangedAction.Reset둘 이상의 항목이 변경 될 때 ICollectionView 클래스에만 인수가 필요하기 때문에 제거하거나 추가 된 항목의 전체 목록을 포함하는 적절한 이벤트 인수를 다른 모든 사람에게 제공 할 수 있습니다. 아래는 구현입니다.

public class BaseObservableCollection<T> : ObservableCollection<T>
{
    //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
    private bool _SuppressCollectionChanged = false;

    /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    public BaseObservableCollection() : base(){}
    public BaseObservableCollection(IEnumerable<T> data) : base(data){}

    #region Event Handlers
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if( !_SuppressCollectionChanged )
        {
            base.OnCollectionChanged(e);
            if( CollectionChanged != null )
                CollectionChanged.Invoke(this, e);
        }
    }

    //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
    //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
    //for applications in code, so we actually check the type we're notifying on and pass a customized event args.
    protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if( handlers != null )
            foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
                handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    #endregion

    #region Extended Collection Methods
    protected override void ClearItems()
    {
        if( this.Count == 0 ) return;

        List<T> removed = new List<T>(this);
        _SuppressCollectionChanged = true;
        base.ClearItems();
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    public void Add(IEnumerable<T> toAdd)
    {
        if( this == toAdd )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toAdd )
            Add(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
    }

    public void Remove(IEnumerable<T> toRemove)
    {
        if( this == toRemove )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toRemove )
            Remove(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
    }
    #endregion
}

답변

Ok, 여전히 ObservableCollection이 내가 원하는대로 작동하기를 바라지 만 … 아래 코드는 내가 한 일입니다. 기본적으로 TrulyObservableCollection이라는 새 T 컬렉션을 만들고 Clearing 이벤트를 발생시키는 데 사용한 ClearItems 메서드를 재정의했습니다.

이 TrulyObservableCollection을 사용하는 코드에서이 Clearing 이벤트를 사용 하여 해당 시점에서 컬렉션에 있는 항목을 반복 하여 분리하려는 이벤트에서 분리를 수행합니다.

이 접근 방식이 다른 사람에게도 도움이되기를 바랍니다.

public class TrulyObservableCollection<T> : ObservableCollection<T>
{
    public event EventHandler<EventArgs> Clearing;
    protected virtual void OnClearing(EventArgs e)
    {
        if (Clearing != null)
            Clearing(this, e);
    }

    protected override void ClearItems()
    {
        OnClearing(EventArgs.Empty);
        base.ClearItems();
    }
}

답변

하나의 이벤트에 등록하고 이벤트 핸들러에서 모든 추가 및 제거를 처리하기를 원했기 때문에이 문제를 약간 다른 방식으로 다루었습니다. 컬렉션 변경 이벤트를 재정의하고 재설정 작업을 항목 목록이있는 제거 작업으로 리디렉션하기 시작했습니다. 관찰 가능한 컬렉션을 컬렉션 뷰의 항목 소스로 사용하고 “범위 작업이 지원되지 않음”을 얻었으므로이 모든 것이 잘못되었습니다.

마침내 내장 버전이 작동 할 것으로 예상 한 방식으로 작동하는 CollectionChangedRange라는 새 이벤트를 만들었습니다.

이 제한이 왜 허용되는지 상상할 수 없으며이 게시물이 적어도 내가 한 막 다른 골목으로 내려가는 다른 사람들을 막기를 바랍니다.

/// <summary>
/// An observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
    private bool _addingRange;

    [field: NonSerialized]
    public event NotifyCollectionChangedEventHandler CollectionChangedRange;

    protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e)
    {
        if ((CollectionChangedRange == null) || _addingRange) return;
        using (BlockReentrancy())
        {
            CollectionChangedRange(this, e);
        }
    }

    public void AddRange(IEnumerable<T> collection)
    {
        CheckReentrancy();
        var newItems = new List<T>();
        if ((collection == null) || (Items == null)) return;
        using (var enumerator = collection.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                _addingRange = true;
                Add(enumerator.Current);
                _addingRange = false;
                newItems.Add(enumerator.Current);
            }
        }
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems));
    }

    protected override void ClearItems()
    {
        CheckReentrancy();
        var oldItems = new List<T>(this);
        base.ClearItems();
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems));
    }

    protected override void InsertItem(int index, T item)
    {
        CheckReentrancy();
        base.InsertItem(index, item);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
    }

    protected override void MoveItem(int oldIndex, int newIndex)
    {
        CheckReentrancy();
        var item = base[oldIndex];
        base.MoveItem(oldIndex, newIndex);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
    }

    protected override void RemoveItem(int index)
    {
        CheckReentrancy();
        var item = base[index];
        base.RemoveItem(index);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
    }

    protected override void SetItem(int index, T item)
    {
        CheckReentrancy();
        var oldItem = base[index];
        base.SetItem(index, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index));
    }
}

/// <summary>
/// A read only observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T>
{
    [field: NonSerialized]
    public event NotifyCollectionChangedEventHandler CollectionChangedRange;

    public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list)
    {
        list.CollectionChangedRange += HandleCollectionChangedRange;
    }

    private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e)
    {
        OnCollectionChangedRange(e);
    }

    protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args)
    {
        if (CollectionChangedRange != null)
        {
            CollectionChangedRange(this, args);
        }
    }

}