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

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

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

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

自己做一个快速的实现:

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

编辑2:添加处理的ClearItems,谢谢乔希

编辑3:添加了一个正确的取消订阅PropertyChanged,谢谢马克

编辑4:哇,这是真正的学习,你去:)。 KP注意到事件是由发件人收集的,而不是在元素发生变化时被触发。 他build议在标有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 

从样本中可以看出,“覆盖”事件有副作用,因此在订阅事件时需要非常小心使用哪种types的variables,因为这会决定接收哪些事件。

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

我会改变添加/删除事件处理程序行如下所示:

 element.PropertyChanged += ContainedElementChanged; 

 element.PropertyChanged -= ContainedElementChanged; 

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

 private void ContainedElementChanged(object sender, PropertyChangedEventArgs e) 

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

如果你想使用内build的东西,你可以使用FreezableCollection 。 那么你会想听听Changed事件 。

在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) { } } 

我会使用ReactiveUI的 ReactiveCollection

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

查看C5通用收集库 。 它的所有集合都包含事件,当添加,删除,插入,清除或集合更改时,可以使用这些事件附加callback。

我正在为这个库的一些扩展工作,在不久的将来应该允许“预览”事件,可以让你取消添加或更改。

@ soren.enemaerke为了发表正确的代码作为答复,因为您的答案的评论部分将使其不可读。 我遇到的唯一问题是触发PropertyChanged事件的特定元素会丢失,您无法在PropertyChanged调用中知道。

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

为了解决这个问题,我创build了一个新的类PropertyChangedEventArgsEx并在你的类中改变了ContainedElementChanged方法。

新class

 public class PropertyChangedEventArgsEx : PropertyChangedEventArgs { public object Sender { get; private set; } public PropertyChangedEventArgsEx(string propertyName, object sender) : base(propertyName) { this.Sender = sender; } } 

改变你的class级

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

在这之后,你可以通过将ePropertyChangedEventArgsEx来得到col.PropertyChanged += (s, e)实际的Sender元素

 ((INotifyPropertyChanged)col).PropertyChanged += (s, e) => { var argsEx = (PropertyChangedEventArgsEx)e; Trace.WriteLine(argsEx.Sender.ToString()); }; 

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

Rxx 2.0包含运算符 ,与ObservableCollection<T>这个转换运算 符一起使您可以轻松实现您的目标。

 ObservableCollection<MyClass> collection = ...; var changes = collection.AsCollectionNotifications<MyClass>(); var itemChanges = changes.PropertyChanges(); var deepItemChanges = changes.PropertyChanges( item => item.ChildItems.AsCollectionNotifications<MyChildClass>()); 

MyClassMyChildClass支持以下属性更改通知模式:

  • INotifyPropertyChanged的
  • [属性]更改事件模式(遗留,用于组件模型)
  • WPF依赖项属性