Observablecollection and Item Propertychanged

ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)

The ContentList's Set method will not get called when you change a value inside the collection, instead you should be looking out for the CollectionChanged event firing.

public class CollectionViewModel : ViewModelBase
{
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
}

public CollectionViewModel()
{
_contentList = new ObservableCollection<EntityViewModel>();
_contentList.CollectionChanged += ContentCollectionChanged;
}

public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//This will get called when the collection is changed
}
}

Okay, that's twice today I've been bitten by the MSDN documentation being wrong. In the link I gave you it says:

Occurs when an item is added, removed,
changed, moved, or the entire list is
refreshed.

But it actually doesn't fire when an item is changed. I guess you'll need a more bruteforce method then:

public class CollectionViewModel : ViewModelBase
{
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
}

public CollectionViewModel()
{
_contentList = new ObservableCollection<EntityViewModel>();
_contentList.CollectionChanged += ContentCollectionChanged;
}

public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(EntityViewModel item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(EntityViewModel item in e.NewItems)
{
//Added items
item.PropertyChanged += EntityViewModelPropertyChanged;
}
}
}

public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//This will get called when the property of an object inside the collection changes
}
}

If you are going to need this a lot you may want to subclass your own ObservableCollection that triggers the CollectionChanged event when a member triggers its PropertyChanged event automatically (like it says it should in the documentation...)

Observable Collection Property Changed on Item in the Collection

Brute force:

  1. Attach handler to each PropertyChanged event for each child item
  2. Grab the ListCollectionView from your CollectionViewSource
  3. Call Refresh.

EDIT:

The code for 1, 2 would live in your code-behind.

For #1, you'd do something like:

private void Source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach( SomeItem item in e.NewItems)
{
item.PropertyChanged += new PropertyChangedEventHandler(_SomeItem_PropertyChanged);
}
break;
....
**HANDLE OTHER CASES HERE**
....
}
}

For #2, in your CollectionChanged handler, you would do something like:

private void _SomeItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
ListCollectionView lcv = (ListCollectionView)(CollectionViewSource.GetDefaultView(theListBox.ItemsSource));
lcv.Refresh();
}

EDIT2:
However, in this case, I would strongly suggest that you also check ListCollectionView.NeedsRefresh and only refresh if that is set. There's no reason to re-sort if your properties have changed which don't affect the sort.

ObservableCollection that fires when containing items change

Your TextBlock that bound to HolmList[0].IsOnline didn't update because IsOnline on Holm didn't notify that its value changed.

You can listen to TestSensor's PropertyChanged event in TestSensor and notify IsOnline property change when one of TestSensor's IsOnline property change.

class Holm : ModelBase
{
public Holm(String Name, TestSensor sensor1, TestSensor sensor2)
{
Sensor1 = sensor1;
Sensor2 = sensor2;
this.Name = Name;

Sensor1.PropertyChanged += OnSensorOnlineChanged;
Sensor2.PropertyChanged += OnSensorOnlineChanged;
}

private void OnSensorOnlineChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsOnline")
{
OnPropertyChanged(nameof(IsOnline));
}
}
}

The nameof keyword

Observable collection item property changed

Your Column class needs to implement INotifyPropertyChanged (which you say it does). You also need to raise that event it when the value of Anonymize changes (which you don't).

Observe PropertyChanged on items in a collection

Ultimate solution discovered

I've found a solution that allows the user to both capitalize on the efficiency of adding or removing many items at a time while only firing one event - and satisfy the needs of UIElements to get the Action.Reset event args while all other users would like a list of elements added and removed.

This solution involves overriding the CollectionChanged event. When we go to fire this event, we can actually look at the target of each registered handler and determine their type. Since only ICollectionView classes require NotifyCollectionChangedAction.Reset args when more than one item changes, we can single them out, and give everyone else proper event args that contain the full list of items removed or added. Below is the implementation.

public class BaseObservableCollection<T> : ObservableCollection<T>
{
//Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
private bool _SuppressCollectionChanged = false;

/// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
public override event NotifyCollectionChangedEventHandler CollectionChanged;

public BaseObservableCollection() : base(){}
public BaseObservableCollection(IEnumerable<T> data) : base(data){}

#region Event Handlers
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if( !_SuppressCollectionChanged )
{
base.OnCollectionChanged(e);
if( CollectionChanged != null )
CollectionChanged.Invoke(this, e);
}
}

//CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
//one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
//for applications in code, so we actually check the type we're notifying on and pass a customized event args.
protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
if( handlers != null )
foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#endregion

#region Extended Collection Methods
protected override void ClearItems()
{
if( this.Count == 0 ) return;

List<T> removed = new List<T>(this);
_SuppressCollectionChanged = true;
base.ClearItems();
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}

public void Add(IEnumerable<T> toAdd)
{
if( this == toAdd )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

_SuppressCollectionChanged = true;
foreach( T item in toAdd )
Add(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
}

public void Remove(IEnumerable<T> toRemove)
{
if( this == toRemove )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

_SuppressCollectionChanged = true;
foreach( T item in toRemove )
Remove(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
}
#endregion
}

Thanks to everyone for their suggestions and links. I never would have gotten to this point without seeing all the incrementally better solutions other people came up with.

Update ObservableCollection on item property change

You are right, that you need to attach to PropertyChanged event of each item. But you cannot relly on CollectionChanged event when subscribing. For example, when you clear the collection, old items are not in the event arguments. Also items can be passed to ObservableCollection ctor.

Better way is to override methods ClearItems, RemoveItem, InsertItem, SetItem

There are already some implementations, e.g.:

  • http://www.codeproject.com/Tips/694370/How-to-Listen-to-Property-Chang

Of course I have written my own some time ago :)
But since WPF introduced live shaping I don't really need it:

  • MSDN: Repositioning data as the data's values change (Live shaping)
  • WPF 4.5 – Part 10 : Live shaping (live filtering, grouping and sorting of collections)

Edit: Dont forget to fire propertychanged event when property changes, just like @Bjørn-Roger Kringsjå proposed. Based on comments, your problem is not in observablecollection, but INotifyPropertyChanged implementation of your class

Notify item changes in observable collection in WPF/C#

You should implement it yourself, my example of observable collection (and also you need to subscribe and Raise OnPropertyChanged(nameof(Total))) when collection item was changed, or change my implementation of collectionEx to raising collection changed event.

    public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public ObservableCollectionEx(IEnumerable<T> initialData) : base(initialData)
{
Init();
}

public ObservableCollectionEx()
{
Init();
}

private void Init()
{
foreach (T item in Items)
item.PropertyChanged += ItemOnPropertyChanged;

CollectionChanged += FullObservableCollectionCollectionChanged;
}

private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (T item in e.NewItems)
{
if (item != null)
item.PropertyChanged += ItemOnPropertyChanged;
}
}

if (e.OldItems != null)
{
foreach (T item in e.OldItems)
{
if (item != null)
item.PropertyChanged -= ItemOnPropertyChanged;
}
}
}

private void ItemOnPropertyChanged(object sender, PropertyChangedEventArgs e)
=> ItemChanged?.Invoke(sender, e);

public event PropertyChangedEventHandler ItemChanged;
}

How to make an ObservableCollection update when an item property is changed

Your class MyFeature needs to declare that it implements interface INotifyPropertyChanged. Otherwise, there will be no listener generated from XAML to listen to your property change notification.

Beside, from your example, I see no use of notifying VisibleFeatures change.



Related Topics



Leave a reply



Submit