Observablecollection That Also Monitors Changes on the Elements in Collection

ObservableCollection that also monitors changes on the elements in collection

Made a quick implementation myself:

public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
Unsubscribe(e.OldItems);
Subscribe(e.NewItems);
base.OnCollectionChanged(e);
}

protected override void ClearItems()
{
foreach(T element in this)
element.PropertyChanged -= ContainedElementChanged;

base.ClearItems();
}

private void Subscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged += ContainedElementChanged;
}
}

private void Unsubscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged -= ContainedElementChanged;
}
}

private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
}
}

Admitted, it would be kind of confusing and misleading to have the PropertyChanged fire on the collection when the property that actually changed is on a contained element, but it would fit my specific purpose. It could be extended with a new event that is fired instead inside ContainerElementChanged

Thoughts?

EDIT: Should note that the BCL ObservableCollection only exposes the INotifyPropertyChanged interface through an explicit implementation so you would need to provide a cast in order to attach to the event like so:

ObservableCollectionEx<Element> collection = new ObservableCollectionEx<Element>();
((INotifyPropertyChanged)collection).PropertyChanged += (x,y) => ReactToChange();

EDIT2: Added handling of ClearItems, thanks Josh

EDIT3: Added a correct unsubscribe for PropertyChanged, thanks Mark

EDIT4: Wow, this is really learn-as-you-go :). KP noted that the event was fired with the collection as sender and not with the element when the a contained element changes. He suggested declaring a PropertyChanged event on the class marked with new. This would have a few issues which I'll try to illustrate with the sample below:

  // work on original instance
ObservableCollection<TestObject> col = new ObservableCollectionEx<TestObject>();
((INotifyPropertyChanged)col).PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

var test = new TestObject();
col.Add(test); // no event raised
test.Info = "NewValue"; //Info property changed raised

// working on explicit instance
ObservableCollectionEx<TestObject> col = new ObservableCollectionEx<TestObject>();
col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

var test = new TestObject();
col.Add(test); // Count and Item [] property changed raised
test.Info = "NewValue"; //no event raised

You can see from the sample that 'overriding' the event has the side effect that you need to be extremely careful of which type of variable you use when subscribing to the event since that dictates which events you receive.

ObservableCollections and changes to properties in C#

Implement the INotifyPropertyChanged interface on your Job class. This should then allow you to use the PropertyChanged on your ObservableCollection<Job>.

To fully support transferring data values from binding source objects
to binding targets, each object in your collection that supports
bindable properties must implement an appropriate property changed
notification mechanism such as the INotifyPropertyChanged interface.

ObservableCollection of objects containing Lists

Yes it is expected behaviour. The observable collection only notifies of changes to its own contents - that is add, delete, reorder.

What you are looking at is a change to an element in the observablecollection - if you want to see your changes to the class you put in, your element has to implement INotifyPropertyChanged.

So currently: If your list property on you complex object changes you won't see it, however if you change that too to be an observablecollection you could see changes to that collection in a sub-itemscontrol like a combobox - but not if you change the collection object to another one - so if you do not implement INotifyPropertyChanged you should set the collectionproperty before the binding is applied.

ObservableCollection CollectionChanged not helpful in WPF MVVM

so, basically, what you are seeing is due to a common mis-conception about ObservableCollection. OC does NOT notify when objects that it contains are changed. It notifies when IT changes (take a look at INotifyCollectionChanged)--when items are added, removed, etc.

what you want to do is be notified when a StockItem that is contained in the OC changes. You are going to have to do a couple of things

1) make sure INotifyPropertyChanged (which you say you are already doing) is implemented on your StockItem

2) customize or find an implementation of ObservableCollection that will notify when an item contained in the collection changes (here is one)

WPF ObservableCollection Edit Mode

You could make a deep copy of the object you want to edit. This way, you can act on the copy while editing, without interfering with the original that remains in the list. Once you`re done editing, you can replace the original by the edited version or rollback.

How to detect property changes in items contained in ObservableCollection T

ObservableCollection.CollectionChanged is raised when an item is added to, or removed from, the collection. ObservableCollection also implements INotifyPropertyChanged, only to raise notifications for changes of its own personal properties -- and so will also raise a PropertyChanged event for its Count property when an item is added or removed (you've got zero reason to care about that right now, but we may as well toss it out there for what it's worth).

So: Your ObservableCollection of Employee won't raise any events when one of its containees has a property change, regardless of whether or not the containee implements INotifyPropertyChanged. The containee should implement INotifyPropertyChanged itself, and raise events when its own property values change -- but an ObservableCollection containing it won't listen for those events. We don't need to notify absolutely everybody about absolutely everything.

But you do need to know when activeDuty changes. Easy.

When you create new Employee instances, you could handle their PropertyChanged events with your OnItemPropertyChanged handler:

//  Fred's not what you'd call active.
var fred = new Employee { Name = "Fred", activeDuty = false };

fred.PropertyChanged += OnItemPropertyChanged;

models.Add(fred);

If Employee implements INotifyPropertyChanged correctly, any detectable increase in Fred's activity level will be perceived immediately in OnItemPropertyChanged. The object sender parameter will be Fred, and e.PropertyName will be the string "activeDuty".

public class Employee : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private bool _activeDuty = false;
public bool activeDuty {
get { return _activeDuty; }
set {
_activeDuty = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(activeDuty)));
}
}

// Name implemented similarly -- either that, or give it a protected
// setter and initialize it in the constructor, to prevent accidents.
}

I don't think you need to be handling models.CollectionChanged unless random other viewmodels could be adding to it. If they could be, then that's a very handy place to put PropertyChanged handlers on new Employees.



Related Topics



Leave a reply



Submit