ObservableCollection, который также отслеживает изменения элементов в коллекции

Есть ли коллекция (BCL или другая), которая имеет следующие характеристики:

Отправляет событие, если коллекция изменена И отправляет событие, если какой-либо из элементов в коллекции отправляет событие PropertyChanged . Тип ObservableCollection где T: INotifyPropertyChanged и коллекция также контролирует элементы для изменений.

Я мог бы обернуть наблюдаемую коллекцию самостоятельно и сделать подписку на конференцию / отмену подписки, когда элементы в коллекции будут добавлены / удалены, но мне просто интересно, сделали ли какие-либо существующие коллекции уже это?

Сделал быструю реализацию:

 public class ObservableCollectionEx : ObservableCollection 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

Мысли?

EDIT: Следует отметить, что BCL ObservableCollection предоставляет только интерфейс INotifyPropertyChanged через явную реализацию, поэтому вам необходимо предоставить подборку, чтобы прикрепить к событию так:

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

EDIT2: Добавлена ​​обработка ClearItems, спасибо Джошу

EDIT3: добавлена ​​правильная отмена подписки на PropertyChanged, спасибо Mark

EDIT4: Вау, это действительно учиться-как-ты-го! :). КП отметил, что событие было запущено с коллекцией в качестве отправителя, а не с элементом, когда элемент, содержащий элемент, изменяется. Он предложил объявить событие PropertyChanged в classе, отмеченном новым . У этого было бы несколько проблем, которые я попытаюсь проиллюстрировать с помощью примера ниже:

  // work on original instance ObservableCollection col = new ObservableCollectionEx(); ((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 col = new ObservableCollectionEx(); 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 

Вы можете видеть из примера, что «переопределение» события имеет побочный эффект, который вам нужно очень осторожно относить к типу переменной, которую вы используете при подписке на событие, поскольку это определяет, какие события вы получаете.

@ soren.enemaerke: Я бы сделал этот комментарий на вашем ответе, но я не могу (я не знаю, почему, может быть, потому, что у меня нет много точек ответа). Во всяком случае, я просто подумал, что я бы сказал, что в вашем коде, который вы опубликовали, я не думаю, что Unsubscribe будет работать правильно, потому что он создает новую встроенную lambda, а затем пытается удалить обработчик событий для нее.

Я бы изменил строки обработчика добавления / удаления событий на что-то вроде:

 element.PropertyChanged += ContainedElementChanged; 

а также

 element.PropertyChanged -= ContainedElementChanged; 

Затем измените подпись метода ContainedElementChanged на:

 private void ContainedElementChanged(object sender, PropertyChangedEventArgs e) 

Это будет означать, что удаление относится к тому же обработчику, что и добавление, а затем удаляет его правильно. Надеюсь, это поможет кому-то 🙂

Если вы хотите использовать что-то встроенное в фреймворк, вы можете использовать FreezableCollection . Затем вы захотите прослушать событие «Изменено» .

Происходит при изменении Freezable или объекта, который он содержит.

Вот небольшой пример. Метод collection_Changed будет вызван дважды.

 public partial class Window1 : Window { public Window1() { InitializeComponent(); FreezableCollection collection = new FreezableCollection(); 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) { } } 

Я бы использовал ReactiveCollection от ReactiveCollection :

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

Ознакомьтесь с библиотекой коллекций C5 Generic Collection . Все его коллекции содержат события, которые можно использовать для присоединения обратных вызовов, когда элементы добавляются, удаляются, вставляются, очищаются или когда коллекция изменяется.

Я сейчас работаю над некоторыми расширениями этой библиотеки, которые в ближайшем будущем должны предусматривать «предварительные» события, которые могут позволить вам отменить добавление или изменение.

@ soren.enemaerke Сделал это ответ, чтобы опубликовать правильный код, так как раздел комментариев вашего ответа сделает его нечитаемым. Единственная проблема, с которой я столкнулся в этом решении, – это тот факт, что конкретный элемент, который запускает событие PropertyChanged потерян, и вы не можете узнать в вызове PropertyChanged .

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

Чтобы исправить это, я создал новый class PropertyChangedEventArgsEx и изменил метод ContainedElementChanged в вашем classе.

новый 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); } 

После этого вы можете получить фактический элемент Sender в col.PropertyChanged += (s, e) e в PropertyChangedEventArgsEx

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

Опять же, вы должны отметить, что здесь представлена ​​коллекция элементов, а не фактический элемент, вызвавший событие. Следовательно, новое свойство Sender в classе PropertyChangedEventArgsEx .

Rxx 2.0 содержит операторы, которые вместе с этим оператором преобразования для ObservableCollection позволяют легко достичь вашей цели.

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

Для MyClass и MyChildClass поддерживаются следующие свойства шаблонов уведомлений:

  • INotifyPropertyChanged
  • [Свойство] Измененный шаблон события (устаревший, для использования с помощью модели компонентов)
  • Свойства зависимостей WPF

Самый простой способ сделать это – просто сделать

 using System.ComponentModel; public class Example { BindingList _collection; public Example() { _collection = new BindingList(); _collection.ListChanged += Collection_ListChanged; } void Collection_ListChanged(object sender, ListChangedEventArgs e) { MessageBox.Show(e.ListChangedType.ToString()); } } 

Класс BindingList как в .net sence 2.0. Он будет ListChanged событие ListChanged любое время, когда элемент в коллекции запускает INotifyPropertyChanged .

Interesting Posts

Windows 7 – изменение настроек региона и языка с помощью сценария

Перечисление карты в JPA с фиксированными значениями?

Может ли лицензия, купленная в одной стране, использоваться в другой стране?

Могу ли я выкачать в Mercurial?

Программно свернуть группу в ExpandableListView

Excel беспорядок с моей формулой при копировании, как я могу остановить, я перестаю менять одну часть, но не другую?

Скрытые особенности ASP.NET

Есть ли getchas с использованием varargs со ссылочными параметрами

Почему sizeof (bool) не определяется как один, самим стандартом?

Установка даты создания файла в Mac OS X

PHP-тернарный оператор против оператора нулевой коалесценции

Файл Android APK: перезапустите старый файл APK в Google Play Store

Стоит ли использовать блоки try-catch, даже если исключение никогда не бросается?

Windows 8 перестает загружаться после установки Visual Studio 2012 Desktop

Ли FileVault2 на OSX Lion шифрует разделы bootcamp?

Давайте будем гением компьютера.