ObservableCollection e Item PropertyChanged

Ho visto molte discussioni su questa domanda, ma forse sono solo un principiante per averlo. Se ho una collezione osservabile che è una raccolta di “PersonNames” come nell’esempio di msdn ( http: //msdn.microsoft.com/en-us/library/ms748365.aspx ), ottengo aggiornamenti alla mia vista se PersonName viene aggiunto o rimosso, ecc. Voglio ottenere un aggiornamento alla mia vista quando cambio una proprietà anche in PersonName . Mi piace se cambio il nome. Posso implementare OnPropertyChanged per ogni proprietà e questa class deriva da INotifyPropertyChanged e sembra essere chiamata come previsto. La mia domanda è, in che modo la Vista ottiene i dati aggiornati da ObservableCollection mentre la proprietà modificata non causa alcun evento per ObservableCollection . Questo è probabilmente qualcosa di veramente semplice, ma perché non riesco a trovare un esempio mi sorprende. Qualcuno può far luce su questo per me o avere dei suggerimenti su esempi che apprezzerei molto. Abbiamo questo scenario in più punti nella nostra attuale app WPF e stiamo lottando per capirlo.


“Generalmente, il codice responsabile della visualizzazione dei dati aggiunge un gestore di eventi PropertyChanged a ciascun object attualmente visualizzato sullo schermo.”

Qualcuno potrebbe darmi un esempio di cosa significa questo? La mia vista si lega al mio ViewModel che ha una ObservableCollection . Questa raccolta è composta da un RowViewModel con proprietà che supportano l’evento PropertiesChanged . Ma non riesco a capire come fare l’aggiornamento della collezione stessa, quindi la mia vista verrà aggiornata.

Ecco come colbind / scolbind l’evento PropertyChanged di ogni object.

 ObservableCollection items = new ObservableCollection(); items.CollectionChanged += items_CollectionChanged; static void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (INotifyPropertyChanged item in e.OldItems) item.PropertyChanged -= item_PropertyChanged; } if (e.NewItems != null) { foreach (INotifyPropertyChanged item in e.NewItems) item.PropertyChanged += item_PropertyChanged; } } static void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { throw new NotImplementedException(); } 

Abbiamo scritto questo nella chat WPF:

 public class OcPropertyChangedListener : INotifyPropertyChanged where T : INotifyPropertyChanged { private readonly ObservableCollection _collection; private readonly string _propertyName; private readonly Dictionary _items = new Dictionary(new ObjectIdentityComparer()); public OcPropertyChangedListener(ObservableCollection collection, string propertyName = "") { _collection = collection; _propertyName = propertyName ?? ""; AddRange(collection); CollectionChangedEventManager.AddHandler(collection, CollectionChanged); } private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: AddRange(e.NewItems.Cast()); break; case NotifyCollectionChangedAction.Remove: RemoveRange(e.OldItems.Cast()); break; case NotifyCollectionChangedAction.Replace: AddRange(e.NewItems.Cast()); RemoveRange(e.OldItems.Cast()); break; case NotifyCollectionChangedAction.Move: break; case NotifyCollectionChangedAction.Reset: Reset(); break; default: throw new ArgumentOutOfRangeException(); } } private void AddRange(IEnumerable newItems) { foreach (T item in newItems) { if (_items.ContainsKey(item)) { _items[item]++; } else { _items.Add(item, 1); PropertyChangedEventManager.AddHandler(item, ChildPropertyChanged, _propertyName); } } } private void RemoveRange(IEnumerable oldItems) { foreach (T item in oldItems) { _items[item]--; if (_items[item] == 0) { _items.Remove(item); PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName); } } } private void Reset() { foreach (T item in _items.Keys.ToList()) { PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName); _items.Remove(item); } AddRange(_collection); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(sender, e); } private class ObjectIdentityComparer : IEqualityComparer { public bool Equals(T x, T y) { return object.ReferenceEquals(x, y); } public int GetHashCode(T obj) { return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); } } } public static class OcPropertyChangedListener { public static OcPropertyChangedListener Create(ObservableCollection collection, string propertyName = "") where T : INotifyPropertyChanged { return new OcPropertyChangedListener(collection, propertyName); } } 
  • Eventi deboli
  • Tiene traccia dello stesso articolo che viene aggiunto più volte alla raccolta
  • È come se la proprietà cambiasse gli eventi dei bambini.
  • La class statica è solo per comodità.

Usalo in questo modo:

 var listener = OcPropertyChangedListener.Create(yourCollection); listener.PropertyChanged += (sender, args) => { //do you stuff} 

Conto,

Sono sicuro di aver trovato una soluzione alternativa o una soluzione al problema, ma l’ho pubblicato per chiunque abbia questo problema comune. È ansible sostituire questa class con ObservableCollections che sono raccolte di oggetti che implementano INotifyPropertyChanged. È un po ‘draconiano, perché dice che la lista deve resettare piuttosto che trovare la proprietà / l’elemento che è cambiato, ma per gli elenchi di piccole dimensioni il colpo sulle prestazioni dovrebbe essere inattuabile.

Marc

 using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; namespace WCIOPublishing.Helpers { public class ObservableCollectionWithItemNotify : ObservableCollection where T: INotifyPropertyChanged { public ObservableCollectionWithItemNotify() { this.CollectionChanged += items_CollectionChanged; } public ObservableCollectionWithItemNotify(IEnumerable collection) :base( collection) { this.CollectionChanged += items_CollectionChanged; foreach (INotifyPropertyChanged item in collection) item.PropertyChanged += item_PropertyChanged; } private void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if(e != null) { if(e.OldItems!=null) foreach (INotifyPropertyChanged item in e.OldItems) item.PropertyChanged -= item_PropertyChanged; if(e.NewItems!=null) foreach (INotifyPropertyChanged item in e.NewItems) item.PropertyChanged += item_PropertyChanged; } } private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { var reset = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); this.OnCollectionChanged(reset); } } } 

Come hai scoperto, non esiste un evento a livello di raccolta che indica che una proprietà di un articolo nella raccolta è cambiata. Generalmente, il codice responsabile della visualizzazione dei dati aggiunge un gestore di eventi PropertyChanged a ciascun object attualmente visualizzato sullo schermo.

Invece di ObservableCollection usa semplicemente BindingList .
Il codice seguente mostra un collegamento DataGrid a un elenco e alle proprietà dell’object.

        

 using System; using System.ComponentModel; using System.Windows; using System.Windows.Threading; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { var c = new BindingList(); this.DataContext = c; // add new item to list on each timer tick var t = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) }; t.Tick += (s, e) => { if (c.Count >= 10) t.Stop(); c.Add(new Data()); }; t.Start(); } } public class Data : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged = delegate { }; System.Timers.Timer t; static Random r = new Random(); public Data() { // update value on each timer tick t = new System.Timers.Timer() { Interval = r.Next(500, 1000) }; t.Elapsed += (s, e) => { Value = DateTime.Now.Ticks; this.PropertyChanged(this, new PropertyChangedEventArgs("Value")); }; t.Start(); } public long Value { get; private set; } } } 

Di seguito è riportato il codice che fornisce una semplice spiegazione di risposta da @Stack e mostra come BindingList sta osservando se ha modificato un elemento e mostra che ObservableCollection non osserverà la modifica all’interno di un elemento.

 using System; using System.Collections.ObjectModel; using System.ComponentModel; namespace BindingListExample { class Program { public ObservableCollection oc = new ObservableCollection(); public System.ComponentModel.BindingList bl = new BindingList(); public Program() { oc.Add(new MyStruct()); oc.CollectionChanged += CollectionChanged; bl.Add(new MyStruct()); bl.ListChanged += ListChanged; } void ListChanged(object sender, ListChangedEventArgs e) { //Observe when the IsActive value is changed this event is triggered. Console.WriteLine(e.ListChangedType.ToString()); } void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { //Observe when the IsActive value is changed this event is not triggered. Console.WriteLine(e.Action.ToString()); } static void Main(string[] args) { Program pm = new Program(); pm.bl[0].IsActive = false; } } public class MyStruct : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private bool isactive; public bool IsActive { get { return isactive; } set { isactive = value; NotifyPropertyChanged("IsActive"); } } private void NotifyPropertyChanged(String PropertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(PropertyName)); } } } }