ObservableCollection y Item PropertyChanged

He visto muchas conversaciones sobre esta pregunta, pero tal vez soy demasiado novato para obtenerla. Si tengo una colección observable que es una colección de “PersonNames” como en el ejemplo de msdn ( http: //msdn.microsoft.com/en-us/library/ms748365.aspx ), obtengo actualizaciones de mi Vista si un PersonName se agrega o quita, etc. Quiero obtener una actualización de mi Vista cuando también cambio una propiedad en PersonName . Me gusta si cambio el primer nombre. Puedo implementar OnPropertyChanged para cada propiedad y hacer que esta clase se derive de INotifyPropertyChanged y que parece que se llama como se esperaba. Mi pregunta es, ¿cómo obtiene View los datos actualizados de ObservableCollection como la propiedad modificada no causa ningún evento para el ObservableCollection . Esto es probablemente algo realmente simple, pero por qué no puedo encontrar un ejemplo me sorprende. ¿Alguien puede arrojar algo de luz sobre esto para mí o tiene alguna sugerencia de ejemplos que agradecería enormemente? Tenemos este escenario en varios lugares en nuestra aplicación WPF actual y estamos luchando para descubrirlo.


“En general, el código responsable de mostrar los datos agrega un controlador de eventos PropertyChanged a cada objeto que se muestra actualmente en la pantalla”.

¿Podría alguien darme un ejemplo de lo que esto significa? Mi vista se une a mi ViewModel que tiene una ObservableCollection . Esta colección se compone de un RowViewModel que tiene propiedades que admiten el evento PropertiesChanged . Pero no puedo entender cómo hacer la actualización de la colección para que mi vista se actualice.

A continuación, le mostramos cómo adjuntar / separar el evento PropertyChanged de cada elemento.

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

Escribimos esto en el WPF-chat:

 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); } } 
  • Eventos débiles
  • Realiza un seguimiento del mismo artículo que se agrega varias veces a la colección
  • Es ~ burbujas ~ la propiedad cambió los eventos de los niños.
  • La clase estática es solo por conveniencia.

Úselo así:

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

Cuenta,

Estoy seguro de que ya ha encontrado una solución o solución a su problema, pero publiqué esto para cualquier persona con este problema común. Puede sustituir esta clase por ObservableCollections que son colecciones de objetos que implementan INotifyPropertyChanged. Es un poco draconiano, porque dice que la lista necesita Restablecer en lugar de encontrar la propiedad / elemento que ha cambiado, pero para las listas pequeñas, el rendimiento alcanzado no debería ser posible.

Bagazo

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

Como descubrió, no hay ningún evento de nivel de colección que indique que la propiedad de un elemento de la colección ha cambiado. En general, el código responsable de mostrar los datos agrega un controlador de eventos PropertyChanged a cada objeto que se muestra actualmente en la pantalla.

En lugar de ObservableCollection, simplemente use BindingList .
El siguiente código muestra un enlace DataGrid a una lista y a las propiedades del elemento.

        

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

A continuación se muestra el código que proporciona una explicación simple de la respuesta mediante @Stack y muestra cómo BindingList está observando si ha cambiado un elemento y muestra que ObservableCollection no observará el cambio dentro de 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)); } } } }