que c# wpf collections observablecollection inotifypropertychanged

c# - que - Notificar a ObservableCollection cuando el artículo cambie



observablecollection methods (7)

El ObservableCollection y sus derivados aumentan sus cambios de propiedad internamente. El código en su setter solo debe TrulyObservableCollection<MyType> si asigna una nueva TrulyObservableCollection<MyType> a la propiedad MyItemsSource . Es decir, solo debería suceder una vez, desde el constructor.

A partir de ese momento, recibirás notificaciones de cambio de propiedad de la colección, no del colocador en tu modelo de vista.

Encontré en este enlace

ObservableCollection no se da cuenta cuando el elemento en él cambia (incluso con INotifyPropertyChanged)

algunas técnicas para notificar a Observablecollection que un elemento ha cambiado. la TrulyObservableCollection en este enlace parece ser lo que estoy buscando.

public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged { public TrulyObservableCollection() : base() { CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged); } void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (Object item in e.NewItems) { (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged); } } if (e.OldItems != null) { foreach (Object item in e.OldItems) { (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged); } } } void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); OnCollectionChanged(a); } }

Pero cuando intento usarlo, no recibo notificaciones en la colección. No estoy seguro de cómo implementar esto correctamente en mi C # Code:

XAML:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyItemsSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <DataGrid.Columns> <DataGridCheckBoxColumn Binding="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> </DataGrid.Columns> </DataGrid>

ViewModel:

public class MyViewModel : ViewModelBase { private TrulyObservableCollection<MyType> myItemsSource; public TrulyObservableCollection<MyType> MyItemsSource { get { return myItemsSource; } set { myItemsSource = value; // Code to trig on item change... RaisePropertyChangedEvent("MyItemsSource"); } } public MyViewModel() { MyItemsSource = new TrulyObservableCollection<MyType>() { new MyType() { MyProperty = false }, new MyType() { MyProperty = true }, new MyType() { MyProperty = false } }; } } public class MyType : ViewModelBase { private bool myProperty; public bool MyProperty { get { return myProperty; } set { myProperty = value; RaisePropertyChangedEvent("MyProperty"); } } } public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChangedEvent(string propertyName) { if (PropertyChanged != null) { PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); PropertyChanged(this, e); } } }

Cuando ejecuto el programa, tengo la 3 casilla de verificación en falso, verdadero, falso como en la inicialización de la propiedad. pero cuando cambio el estado de uno de los ckeckbox, el programa pasa por item_PropertyChanged pero nunca en el código de propiedad MyItemsSource.


El punto que ha comentado como // Code to trig on item change... solo se activará cuando se cambie el objeto de la colección, como cuando se establece en un nuevo objeto o se establece en nulo.

Con su implementación actual de TrulyObservableCollection, para gestionar los eventos modificados de la propiedad de su colección, registre algo en el evento CollectionChanged de MyItemsSource

public MyViewModel() { MyItemsSource = new TrulyObservableCollection<MyType>(); MyItemsSource.CollectionChanged += MyItemsSource_CollectionChanged; MyItemsSource.Add(new MyType() { MyProperty = false }); MyItemsSource.Add(new MyType() { MyProperty = true}); MyItemsSource.Add(new MyType() { MyProperty = false }); } void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Handle here }

Personalmente, realmente no me gusta esta implementación. Estás generando un evento CollectionChanged que dice que toda la colección se ha restablecido, cada vez que cambia una propiedad. Claro, hará que la UI se actualice en cualquier momento en que cambie un elemento de la colección, pero veo que eso es malo para el rendimiento y no parece poder identificar qué propiedad cambió, que es una de las piezas clave de información. Por lo general, necesito cuando hago algo en PropertyChanged .

Prefiero usar un ObservableCollection regular y simplemente conectar los eventos PropertyChanged a sus artículos en CollectionChanged . Si su interfaz de usuario está vinculada correctamente a los elementos de ObservableCollection , no debería necesitar decirle a la interfaz de usuario que actualice cuando cambia una propiedad de un elemento de la colección.

public MyViewModel() { MyItemsSource = new ObservableCollection<MyType>(); MyItemsSource.CollectionChanged += MyItemsSource_CollectionChanged; MyItemsSource.Add(new MyType() { MyProperty = false }); MyItemsSource.Add(new MyType() { MyProperty = true}); MyItemsSource.Add(new MyType() { MyProperty = false }); } void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) foreach(MyType item in e.NewItems) item.PropertyChanged += MyType_PropertyChanged; if (e.OldItems != null) foreach(MyType item in e.OldItems) item.PropertyChanged -= MyType_PropertyChanged; } void MyType_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "MyProperty") DoWork(); }


Puede usar un método de extensión para recibir notificaciones sobre la propiedad modificada de un elemento en una colección de forma genérica.

public static class ObservableCollectionExtension { public static void NotifyPropertyChanged<T>(this ObservableCollection<T> observableCollection, Action<T, PropertyChangedEventArgs> callBackAction) where T : INotifyPropertyChanged { observableCollection.CollectionChanged += (sender, args) => { //Does not prevent garbage collection says: http://.com/questions/298261/do-event-handlers-stop-garbage-collection-from-occuring //publisher.SomeEvent += target.SomeHandler; //then "publisher" will keep "target" alive, but "target" will not keep "publisher" alive. if (args.NewItems == null) return; foreach (T item in args.NewItems) { item.PropertyChanged += (obj, eventArgs) => { callBackAction((T)obj, eventArgs); }; } }; } } public void ExampleUsage() { var myObservableCollection = new ObservableCollection<MyTypeWithNotifyPropertyChanged>(); myObservableCollection.NotifyPropertyChanged((obj, notifyPropertyChangedEventArgs) => { //DO here what you want when a property of an item in the collection has changed. }); }


Sé que es tarde, pero tal vez esto ayude a otros. NotifyObservableCollection una clase NotifyObservableCollection , que resuelve el problema de la notificación faltante al elemento en sí, cuando cambia una propiedad del elemento. El uso es tan simple como ObservableCollection .

public class NotifyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged { private void Handle(object sender, PropertyChangedEventArgs args) { OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null)); } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (object t in e.NewItems) { ((T) t).PropertyChanged += Handle; } } if (e.OldItems != null) { foreach (object t in e.OldItems) { ((T) t).PropertyChanged -= Handle; } } base.OnCollectionChanged(e); }

Mientras se agregan o eliminan elementos, la clase reenvía los elementos del evento PropertyChanged evento PropertyChanged de colecciones.

uso:

public abstract class ParameterBase : INotifyPropertyChanged { protected readonly CultureInfo Ci = new CultureInfo("en-US"); private string _value; public string Value { get { return _value; } set { if (value == _value) return; _value = value; OnPropertyChanged(); } } } public class AItem { public NotifyObservableCollection<ParameterBase> Parameters { get { return _parameters; } set { NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged(); if (_parameters != null) _parameters.CollectionChanged -= cceh; _parameters = value; //needed for Binding to AItem at xaml directly _parameters.CollectionChanged += cceh; } } public NotifyObservableCollection<ParameterBase> DefaultParameters { get { return _defaultParameters; } set { NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged(); if (_defaultParameters != null) _defaultParameters.CollectionChanged -= cceh; _defaultParameters = value; //needed for Binding to AItem at xaml directly _defaultParameters.CollectionChanged += cceh; } } public class MyViewModel { public NotifyObservableCollection<AItem> DataItems { get; set; } }

Si ahora una propiedad de un elemento en DataItems cambia, la siguiente xaml recibirá una notificación, aunque se enlaza a los Parameters[0] o al elemento en sí, excepto a la propiedad cambiante Value del elemento (los Convertidores en Disparadores se llaman confiables en cada cambio).

<DataGrid CanUserAddRows="False" AutoGenerateColumns="False" ItemsSource="{Binding DataItems}"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Parameters[0].Value}" Header="P1"> <DataGridTextColumn.CellStyle> <Style TargetType="DataGridCell"> <Setter Property="Background" Value="Aqua" /> <Style.Triggers> <DataTrigger Value="False"> <!-- Bind to Items with changing properties --> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource ParameterCompareConverter}"> <Binding Path="DefaultParameters[0]" /> <Binding Path="Parameters[0]" /> </MultiBinding> </DataTrigger.Binding> <Setter Property="Background" Value="DeepPink" /> </DataTrigger> <!-- Binds to AItem directly --> <DataTrigger Value="True" Binding="{Binding Converter={StaticResource CheckParametersConverter}}"> <Setter Property="FontWeight" Value="ExtraBold" /> </DataTrigger> </Style.Triggers> </Style> </DataGridTextColumn.CellStyle> </DataGridTextColumn>


Todas las soluciones aquí son correctas, pero les falta un escenario importante en el que se utiliza el método Clear() , que no proporciona OldItems en el objeto NotifyCollectionChangedEventArgs .

esta es la ObservableCollection perfecta.

public class ObservableCollectionEX<T> : ObservableCollection<T> { #region Constructors public ObservableCollectionEX() : base() { CollectionChanged += ObservableCollection_CollectionChanged; } public ObservableCollectionEX(IEnumerable<T> c) : base(c) { CollectionChanged += ObservableCollection_CollectionChanged; } public ObservableCollectionEX(List<T> l) : base(l) { CollectionChanged += ObservableCollection_CollectionChanged; } #endregion public new void Clear() { foreach (var item in this) { if (item is INotifyPropertyChanged i) { if (i != null) i.PropertyChanged -= Element_PropertyChanged; } } base.Clear(); } private void ObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) foreach (var item in e.OldItems) { if (item != null && item is INotifyPropertyChanged i) { i.PropertyChanged -= Element_PropertyChanged; } } if (e.NewItems != null) foreach (var item in e.NewItems) { if (item != null && item is INotifyPropertyChanged i) { i.PropertyChanged -= Element_PropertyChanged; i.PropertyChanged += Element_PropertyChanged; } } } private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e) { //raise the event ItemPropertyChanged?.Invoke(sender, e); } /// <summary> /// the sender is the Item /// </summary> public PropertyChangedEventHandler ItemPropertyChanged; }

Incluso puede hacer un esfuerzo adicional y cambiar ItemPropertyChanged para proporcionar la lista de propietarios como esta

Fuera de la clase en algún espacio de nombres:
public delegate void ListedItemPropertyChangedEventHandler(IList SourceList, object Item, PropertyChangedEventArgs e);

en la clase cambia esto:

private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e) { //raise the event ItemPropertyChanged?.Invoke(this,sender, e); } public ListedItemPropertyChangedEventHandler ItemPropertyChanged;


Una solución simple es usar BindingList<T> lugar de ObservableCollection<T> . De hecho, el elemento de retransmisión BindingList cambia las notificaciones. Entonces, con una lista de enlaces, si el elemento implementa la interfaz INotifyPropertyChanged entonces simplemente puede obtener notificaciones utilizando el evento ListChanged .

Ver también this respuesta SO.


Resolví este caso usando acción estática

public class CatalogoModel { private String _Id; private String _Descripcion; private Boolean _IsChecked; public String Id { get { return _Id; } set { _Id = value; } } public String Descripcion { get { return _Descripcion; } set { _Descripcion = value; } } public Boolean IsChecked { get { return _IsChecked; } set { _IsChecked = value; NotifyPropertyChanged("IsChecked"); OnItemChecked.Invoke(); } } public static Action OnItemChecked; } public class ReglaViewModel : ViewModelBase { private ObservableCollection<CatalogoModel> _origenes; CatalogoModel.OnItemChecked = () => { var x = Origenes.Count; //Entra cada vez que cambia algo en _origenes }; }