ObservableCollection还监视集合中元素的更改

时间:2008-11-06 15:20:01

标签: c# collections

是否有一个具有以下特征的集合(BCL或其他):

如果更改集合则发送事件如果集合中的任何元素发送PropertyChanged事件,则发送事件。排序ObservableCollection<T>其中T: INotifyPropertyChanged,集合也在监视变更元素。

我可以自己包装一个可观察的集合,并在添加/删除集合中的元素时进行事件订阅/取消订阅但我只是想知道现有的集合是否已经这样做了吗?

8 个答案:

答案 0 :(得分:43)

自己快速实施:

public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        Unsubscribe(e.OldItems);
        Subscribe(e.NewItems);
        base.OnCollectionChanged(e);
    }

    protected override void ClearItems()
    {
        foreach(T element in this)
            element.PropertyChanged -= ContainedElementChanged;

        base.ClearItems();
    }

    private void Subscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged += ContainedElementChanged;
        }
    }

    private void Unsubscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged -= ContainedElementChanged;
        }
    }

    private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged(e);
    }
}

承认,当实际更改的属性位于包含的元素上时,对集合触发PropertyChanged会有点混乱和误导,但这符合我的特定目的。它可以通过在ContainerElementChanged

中触发的新事件进行扩展

思想?

编辑:请注意,BCL ObservableCollection只通过显式实现公开INotifyPropertyChanged接口,因此您需要提供一个强制转换才能附加到事件,如下所示:

ObservableCollectionEx<Element> collection = new ObservableCollectionEx<Element>();
((INotifyPropertyChanged)collection).PropertyChanged += (x,y) => ReactToChange();

EDIT2:添加了对ClearItems的处理,感谢Josh

EDIT3:为PropertyChanged添加了正确的取消订阅,感谢Mark

EDIT4:哇,这真的像你一样学习:)。 KP注意到,当包含的元素发生变化时,事件是以集合作为发送者而不是元素触发的。他建议在标有 new 的类上声明一个PropertyChanged事件。这将有一些问题,我将尝试用下面的示例来说明:

  // work on original instance
  ObservableCollection<TestObject> col = new ObservableCollectionEx<TestObject>();
  ((INotifyPropertyChanged)col).PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // no event raised
  test.Info = "NewValue"; //Info property changed raised

  // working on explicit instance
  ObservableCollectionEx<TestObject> col = new ObservableCollectionEx<TestObject>();
  col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // Count and Item [] property changed raised
  test.Info = "NewValue"; //no event raised

您可以从示例中看到,“覆盖”事件具有副作用,您需要非常小心在订阅事件时使用哪种类型的变量,因为这决定了您收到的事件。

答案 1 :(得分:7)

@ soren.enemaerke: 我会在你的答案帖子上发表这个评论,但我不能(我不知道为什么,也许是因为我没有很多代表点)。无论如何,我只是认为我在你发布的代码中提到我不认为Unsubscribe会正常工作,因为它正在创建一个新的lambda内联,然后尝试删除它的事件处理程序。

我会将添加/删除事件处理程序行更改为:

element.PropertyChanged += ContainedElementChanged;

element.PropertyChanged -= ContainedElementChanged;

然后将ContainedElementChanged方法签名更改为:

private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)

这将识别删除与添加相同的处理程序,然后正确删除它。希望这有助于某人:)

答案 2 :(得分:3)

如果您想使用框架内置的内容,可以使用FreezableCollection。然后你会想听Changed event

  

在Freezable或对象时发生   它包含已修改。

这是一个小样本。 collection_Changed方法将被调用两次。

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        FreezableCollection<SolidColorBrush> collection = new FreezableCollection<SolidColorBrush>();
        collection.Changed += collection_Changed;
        SolidColorBrush brush = new SolidColorBrush(Colors.Red);
        collection.Add(brush);
        brush.Color = Colors.Blue;
    }

    private void collection_Changed(object sender, EventArgs e)
    {
    }
}

答案 3 :(得分:1)

我会使用ReactiveUI's ReactiveCollection

reactiveCollection.Changed.Subscribe(_ => ...);

答案 4 :(得分:0)

查看C5 Generic Collection Library。它的所有集合都包含可用于在添加,删除,插入,清除项目或集合更改时附加回调的事件。

我正在努力对该库here进行一些扩展,以便在不久的将来允许“预览”事件,以便取消添加或更改。

答案 5 :(得分:0)

@ soren.enemaerke这是一个回复,以便发布正确的代码,因为你的答案的评论部分将使其无法阅读。 我对该解决方案唯一的问题是触发PropertyChanged事件的特定元素丢失,您无法在PropertyChanged调用中知道。

col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName)

要解决此问题,我已经创建了一个新课程PropertyChangedEventArgsEx,并更改了班级中的ContainedElementChanged方法。

新课程

public class PropertyChangedEventArgsEx : PropertyChangedEventArgs
{
    public object Sender { get; private set; }

    public PropertyChangedEventArgsEx(string propertyName, object sender) 
        : base(propertyName)
    {
        this.Sender = sender;
    }
}

更改班级

 private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
    {
        var ex = new PropertyChangedEventArgsEx(e.PropertyName, sender);
        OnPropertyChanged(ex);
    }

在此之后,您可以通过将Sender投射到col.PropertyChanged += (s, e)

来获取e中的实际PropertyChangedEventArgsEx元素
((INotifyPropertyChanged)col).PropertyChanged += (s, e) =>
        {
            var argsEx = (PropertyChangedEventArgsEx)e;
            Trace.WriteLine(argsEx.Sender.ToString());
        };

同样,您应该注意s这里是元素的集合,而不是触发事件的实际元素。因此,Sender类中的新PropertyChangedEventArgsEx属性。

答案 6 :(得分:0)

Rxx 2.0包含operators以及ObservableCollection<T> ObservableCollection<MyClass> collection = ...; var changes = collection.AsCollectionNotifications<MyClass>(); var itemChanges = changes.PropertyChanges(); var deepItemChanges = changes.PropertyChanges( item => item.ChildItems.AsCollectionNotifications<MyChildClass>()); ,可以轻松实现目标。

MyClass

MyChildClass和{{1}}支持以下属性更改通知模式:

  • conversion operator
  • [属性]更改了事件模式(遗留,供组件模型使用)
  • WPF依赖项属性

答案 7 :(得分:0)

最简单的方法就是做

using System.ComponentModel;
public class Example
{
    BindingList<Foo> _collection;

    public Example()
    {
        _collection = new BindingList<Foo>();
        _collection.ListChanged += Collection_ListChanged;
    }

    void Collection_ListChanged(object sender, ListChangedEventArgs e)
    {
        MessageBox.Show(e.ListChangedType.ToString());
    }

}

BindingList类已经在.net sence 2.0中。只要集合中的项目触发ListChanged,它就会触发INotifyPropertyChanged事件。