ObservableCollection che monitora anche le modifiche sugli elementi nella raccolta

Esiste una collezione (BCL o altro) con le seguenti caratteristiche:

Invia un evento se la raccolta viene modificata E invia un evento se uno qualsiasi degli elementi nella raccolta invia un evento PropertyChanged . Ordinamento di ObservableCollection dove T: INotifyPropertyChanged e la raccolta sta monitorando anche gli elementi per le modifiche.

Potrei racchiudere una raccolta osservabile e fare in modo che l’evento si iscriva / disiscriva quando gli elementi della raccolta vengono aggiunti / rimossi, ma mi stavo chiedendo se le collezioni esistenti lo facessero già?

Fatto una rapida implementazione da solo:

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

Ammesso, sarebbe un po ‘confuso e fuorviante avere il fuoco PropertyChanged sulla raccolta quando la proprietà che è effettivamente cambiata si trova su un elemento contenuto, ma si adatterebbe al mio scopo specifico. Potrebbe essere esteso con un nuovo evento che viene generato invece all’interno di ContainerElementChanged

Pensieri?

EDIT: Si noti che la BCL ObservableCollection espone solo l’interfaccia INotifyPropertyChanged attraverso un’implementazione esplicita quindi è necessario fornire un cast per collegarsi all’evento in questo modo:

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

EDIT2: aggiunta gestione di ClearItems, grazie Josh

EDIT3: Aggiunto un corretto disiscrizione per PropertyChanged, grazie Mark

EDIT4: Wow, questo è davvero imparare-come-vai-vai :). KP ha notato che l’evento è stato triggersto con la raccolta come mittente e non con l’elemento quando l’elemento contenuto cambia. Ha suggerito di dichiarare un evento PropertyChanged sulla class contrassegnata con new . Questo avrebbe alcuni problemi che cercherò di illustrare con l’esempio seguente:

  // 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 

Puoi vedere dal campione che “l’override” dell’evento ha l’effetto collaterale che devi essere estremamente attento a quale tipo di variabile usi quando ti iscrivi all’evento dal momento che detta gli eventi che ricevi.

@ soren.enemaerke: avrei fatto questo commento sul tuo post di risposta, ma non posso (non so perché, forse perché non ho molti punti rep). Ad ogni modo, ho pensato che avrei detto che nel tuo codice che hai postato non penso che l’Unsubscribe funzionerebbe correttamente perché sta creando un nuovo lambda inline e poi provando a rimuovere il gestore di eventi per esso.

Vorrei cambiare le linee di gestione dell’evento Aggiungi / Rimuovi a qualcosa del tipo:

 element.PropertyChanged += ContainedElementChanged; 

e

 element.PropertyChanged -= ContainedElementChanged; 

Quindi modifica la firma del metodo ContainedElementChanged su:

 private void ContainedElementChanged(object sender, PropertyChangedEventArgs e) 

Ciò riconoscerebbe che la rimozione è per lo stesso gestore come l’aggiunta e quindi rimuoverlo correttamente. Spero che questo aiuti qualcuno 🙂

Se si desidera utilizzare qualcosa integrato nel framework, è ansible utilizzare FreezableCollection . Quindi vorrai ascoltare l’ evento Changed .

Si verifica quando viene modificato il Freezable o un object che contiene.

Ecco un piccolo esempio. Il metodo collection_Changed verrà chiamato due volte.

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

Vorrei utilizzare ReactiveCollection di ReactiveCollection :

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

Controlla la libreria di raccolta generica C5 . Tutte le raccolte contengono eventi che è ansible utilizzare per albind i callback quando gli articoli vengono aggiunti, rimossi, inseriti, cancellati o quando la raccolta cambia.

Sto lavorando per alcune estensioni a questa libreria che nel prossimo futuro dovrebbero consentire eventi di “anteprima” che potrebbero consentire di annullare un add o un cambiamento.

@ soren.enemaerke Questa è una risposta per poter pubblicare un codice corretto dato che la sezione commenti sulla tua risposta lo renderebbe illeggibile. L’unico problema che ho riscontrato con la soluzione è che il particolare elemento che triggers l’evento PropertyChanged va perso e non hai modo di sapere nella chiamata PropertyChanged .

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

Per risolvere questo problema ho creato una nuova class PropertyChangedEventArgsEx e modificato il metodo ContainedElementChanged all’interno della class.

nuova class

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

modifiche alla tua class

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

Dopodiché puoi ottenere l’effettivo elemento Sender in col.PropertyChanged += (s, e) lanciando e su PropertyChangedEventArgsEx

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

Di nuovo, dovresti notare che la s è la raccolta di elementi, non l’elemento reale che ha triggersto l’evento. Da qui la nuova proprietà Sender nella class PropertyChangedEventArgsEx .

Rxx 2.0 contiene operatori che insieme a questo operatore di conversione di ObservableCollection rende facile raggiungere il tuo objective.

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

La seguente proprietà ha modificato i pattern di notifica per MyClass e MyChildClass :

  • INotifyPropertyChanged
  • [Proprietà] Pattern di eventi modificati (legacy, per uso da parte di Model)
  • Proprietà di dipendenza WPF

Il modo più semplice per farlo è farlo

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

La class BindingList come stata in .net sence 2.0. ListChanged evento ListChanged ogni volta che un object della raccolta spara a INotifyPropertyChanged .