观察集合中项目的PropertyChanged

Ala*_*ain 11 c# wpf .net-4.0

我正在尝试挂钩INotifyPropertyChanged集合中对象的事件.

我在这个问题上见过的每一个答案都说要按如下方式处理:

void NotifyingItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if( e.NewItems != null )
    {
        foreach( INotifyPropertyChanged item in e.NewItems )
        {
            item.PropertyChanged += new PropertyChangedEventHandler(CollectionItemChanged);
        }
    }
    if( e.OldItems != null )
    {
        foreach( ValidationMessageCollection item in e.OldItems )
        {
            item.PropertyChanged -= CollectionItemChanged;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是,只要开发人员调用Clear()NotifyingItems集合,这就完全失败了.发生这种情况时,该事件处理函数被调用,e.Action == Reset并且两个e.NewItemse.OldItems等于null(我希望后者包含的所有项目).

问题是这些物品不会消失,而且它们不会被破坏,它们不再被当前的班级监控 - 但由于我从未有机会取消映射它们PropertyChangedEventHandler- 它们一直在调用我的CollectionItemChanged处理程序他们已从我的NotifyingItems列表中清除.如何以这种"完善的"模式处理这种情况?

Rac*_*hel 5

或许看看这个答案

它建议不要使用.Clear()和实现一个一个一个地.RemoveAll()删除项目的扩展方法

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

如果这对您不起作用,那么链接中也会发布其他好的解决方案.


Ala*_*ain 2

发现最终解决方案

我找到了一种解决方案,它允许用户既可以利用一次添加或删除多个项目的效率,同时只触发一个事件,又可以满足 UIElements 获取 Action.Reset 事件参数的需求,而所有其他用户都会这样做就像添加和删除的元素列表一样。

此解决方案涉及重写 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
}
Run Code Online (Sandbox Code Playgroud)

感谢大家的建议和链接。如果没有看到其他人提出的所有逐渐更好的解决方案,我永远不会走到这一步。