ObservableCollection, который также отслеживает изменения элементов в коллекции
Есть ли коллекция (BCL или другая), которая имеет следующие характеристики:
Отправляет событие, если коллекция изменена И отправляет событие, если какой-либо из элементов в коллекции отправляет событие PropertyChanged
. Тип ObservableCollection
где T: INotifyPropertyChanged
и коллекция также контролирует элементы для изменений.
Я мог бы обернуть наблюдаемую коллекцию самостоятельно и сделать подписку на конференцию / отмену подписки, когда элементы в коллекции будут добавлены / удалены, но мне просто интересно, сделали ли какие-либо существующие коллекции уже это?
- Linq. Любой VS .Exists - В чем разница?
- Разница между устаревшим и устаревшим API?
- IEnumerable как возвращаемый тип
- Java: как преобразовать HashMap в массив
- Лучше ли возвращать пустую или пустую коллекцию?
- Список Емкость возвращает больше элементов, чем добавлено
- ObservableCollection Не поддерживает метод AddRange, поэтому я получаю уведомление для каждого добавленного элемента, кроме того, что касается INotifyCollectionChanging?
- Почему инициализаторы коллекции C # работают таким образом?
- JSTL c: forEach на странице JSP не работает
- Ограниченная по размеру очередь, содержащая последние N элементов в Java
- Как быстро удалить элементы из списка
Сделал быструю реализацию:
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
.