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 Employee
s.
Related Topics
What's the Point of the Var Keyword
When to Dispose Cancellationtokensource
A Generic List of Anonymous Class
Padding Is Invalid and Cannot Be Removed
How to Calculate Float Type Precision and Does It Make Sense
Adding Placeholder Text to Textbox
How to Run a Winform from Console Application
How to Get First N Elements of a List in C#
Casting: (Newtype) VS. Object as Newtype
How to Assign by "Reference" to a Class Field in C#
"A Project with an Output Type of Class Library Cannot Be Started Directly"
How to Remove All Non Alphanumeric Characters from a String Except Dash
Compare Using Thread.Sleep and Timer for Delayed Execution
Oledbcommand Parameters Order and Priority
How to Get Current User in ASP.NET Core
Change Attribute's Parameter at Runtime
Microsoft.Jet.Oledb.4.0' Provider Is Not Registered on the Local MAChine