Notificar ObservableCollection quando o item é alterado

Eu encontrei neste link

ObservableCollection não percebendo quando o item nele é alterado (mesmo com INotifyPropertyChanged)

algumas técnicas para notificar uma observação de que um item foi alterado. O TrulyObservableCollection neste link parece ser o que eu estou procurando.

public class TrulyObservableCollection : ObservableCollection 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); } } 

Mas quando tento usá-lo, não recebo notifications sobre a coleção. Não tenho certeza de como implementar corretamente isso no meu código c #:

XAML:

       

ViewModel:

 public class MyViewModel : ViewModelBase { private TrulyObservableCollection myItemsSource; public TrulyObservableCollection MyItemsSource { get { return myItemsSource; } set { myItemsSource = value; // Code to trig on item change... RaisePropertyChangedEvent("MyItemsSource"); } } public MyViewModel() { MyItemsSource = new TrulyObservableCollection() { 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); } } } 

Quando executo o programa, tenho a checkbox de seleção 3 para false, true, false, como na boot da propriedade. mas quando eu mudo o estado de um dos ckeckbox, o programa passa por item_PropertyChanged mas nunca no código MyItemsSource Property.

O ponto que você comentou como // Code to trig on item change... só será acionado quando o object de coleção for alterado, como quando ele for definido para um novo object ou definido como nulo.

Com sua implementação atual do TrulyObservableCollection, para manipular os events de propriedade alterada de sua coleção, registre algo para o evento CollectionChanged de MyItemsSource

 public MyViewModel() { MyItemsSource = new TrulyObservableCollection(); 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 } 

Pessoalmente, eu realmente não gosto dessa implementação. Você está levantando um evento CollectionChanged que diz que a coleção inteira foi redefinida, sempre que uma propriedade é alterada. Claro que isso fará com que a atualização da interface do usuário sempre que um item da coleção seja alterado, mas eu vejo isso sendo ruim no desempenho e não parece ter uma maneira de identificar qual propriedade foi alterada, que é uma das principais informações Eu geralmente preciso quando faço algo no PropertyChanged .

Eu prefiro usar um ObservableCollection regular e apenas ligar os events PropertyChanged aos seus itens em CollectionChanged . Se a sua interface do usuário estiver vinculada corretamente aos itens do ObservableCollection , você não precisará dizer à interface do usuário para atualizar quando uma propriedade em um item da coleção for alterada.

 public MyViewModel() { MyItemsSource = new ObservableCollection(); 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(); } 

Eu resolvi este caso usando a ação 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 _origenes; CatalogoModel.OnItemChecked = () => { var x = Origenes.Count; //Entra cada vez que cambia algo en _origenes }; } 

O ObservableCollection e seus derivados aumentam suas mudanças de propriedade internamente. O código em seu setter só deve ser acionado se você atribuir um novo TrulyObservableCollection à propriedade MyItemsSource . Ou seja, isso só deve acontecer uma vez, do construtor.

A partir desse ponto, você receberá notifications de mudança de propriedade da coleção, não do definidor no seu modelo de visualização.

Eu sei que é tarde, mas talvez isso ajude os outros. Eu criei uma class NotifyObservableCollection , que resolve o problema da falta de notificação para o próprio item, quando uma propriedade do item é alterada. O uso é tão simples quanto ObservableCollection .

 public class NotifyObservableCollection : ObservableCollection 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); } 

Enquanto os itens são adicionados ou removidos, a class encaminha os itens PropertyChanged para o evento PropertyChanged das collections.

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 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 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 DataItems { get; set; } } 

Se agora uma propriedade de um item em DataItems alterada, o xaml a seguir receberá uma notificação, embora seja vinculado a Parameters[0] ou ao item em si, exceto à propriedade change Value do item (conversores em Triggers são chamados confiáveis ​​em todos os mudança).

        

Você poderia usar um método de extensão para ser notificado sobre a propriedade alterada de um item em uma coleção de maneira genérica.

 public static class ObservableCollectionExtension { public static void NotifyPropertyChanged(this ObservableCollection observableCollection, Action callBackAction) where T : INotifyPropertyChanged { observableCollection.CollectionChanged += (sender, args) => { //Does not prevent garbage collection says: http://stackoverflow.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(); myObservableCollection.NotifyPropertyChanged((obj, notifyPropertyChangedEventArgs) => { //DO here what you want when a property of an item in the collection has changed. }); } 

Todas as soluções aqui estão corretas, mas estão faltando um cenário importante em que o método Clear () é usado, que não fornece OldItems no object NotifyCollectionChangedEventArgs .

este é o ObservableCollection perfeito.

 public class ObservableCollectionEX : ObservableCollection { #region Constructors public ObservableCollectionEX() : base() { CollectionChanged += ObservableCollection_CollectionChanged; } public ObservableCollectionEX(IEnumerable c) : base(c) { CollectionChanged += ObservableCollection_CollectionChanged; } public ObservableCollectionEX(List 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); } ///  /// the sender is the Item ///  public PropertyChangedEventHandler ItemPropertyChanged; } 

Você pode até mesmo ir a milha extra e alterar o ItemPropertyChanged para fornecer a lista de proprietários como este

Fora da class em algum namespace:
public delegate void ListedItemPropertyChangedEventHandler(IList SourceList, object Item, PropertyChangedEventArgs e);

na turma, mude estes:

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

Uma solução simples é usar BindingList vez de ObservableCollection . Na verdade, o item de retransmissão BindingList altera as notifications. Portanto, com uma lista de binding, se o item implementar a interface INotifyPropertyChanged , você poderá simplesmente receber notifications usando o evento ListChanged .

Veja também esta resposta SO.