From the MSDN about OnCollectionChanged: "Occurs when an item is added, removed, changed, moved, or the entire list is refreshed."
I'm changing a property attached to an obj that resides in my collection, but OnCollectionChanged isn't fired. I am implementing iNotifyPropertyChanged on the obj class.
public class ObservableBatchCollection : ObservableCollection<BatchData>
{
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if(e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (BatchData item in e.NewItems)
{
}
}
base.OnCollectionChanged(e);
}
public ObservableBatchCollection(IEnumerable<BatchData> items)
: base(items)
{
}
}
To me, that reads that when an item in the collection is changed, such as a property of the object, that this event should fire. It's not, however. I want to be able to know when an item in my custom collection changes so I can perform a calculation on it, if needed.
Any thoughts?
ObservableCollection<T> raises events only when the collection itself changes. An item contained in the collection that has its internal state mutated has not altered the structure of the collection, and ObservableCollection<T> will not report it.
One option is to subclass ObservableCollection<T> and subscribe to each item's OnPropertyChanged event when it is added. In that handler, you can raise either a custom event, or fall back to the collection's own PropertyChanged event. Note that if you do go this route, you should add a generic constraint so that T : INotifyPropertyChanged.
Related
I have a an ObservableCollection of Component, a class with another ObservableCollection, a String and a ComponentVersion. The SelectedComponentVersion is being updated via my view correctly but I'm unable to get the property of Components to fire it's setter/OnPropertyChanged.
private ObservableCollection<Component> components
public ObservableCollection<Component> Components
{
get { return foo; }
set { foo = value; OnPropertyChanged(); }
}
Here is the class of Component.
public class Component : ViewModelBase
{
private string componentName;
private ObservableCollection<ComponentVersion> componentVersions;
private ComponentVersion selectedComponent;
public string ComponentName
{
get { return componentName; }
set { componentName = value; OnPropertyChanged(); }
}
public ObservableCollection<ComponentVersion> ComponentVersions
{
get { return componentVersions; }
set { componentVersions = value; OnPropertyChanged(); }
}
public ComponentVersion SelectedComponent
{
get { return selectedComponent; }
set { selectedComponent = value; OnPropertyChanged(); }
}
public Component(string componentName, List<ComponentVersion> componentVersion)
{
ComponentName = componentName;
ComponentVersions = componentVersion.ToObservableCollection();
}
}
I then have a binding from a listview onto the SelectedComponent property inside of Component.
I have read countless stack overflows about setting up CollectionChanged and have tried to implement it with no luck.
Components.CollectionChanged += stuff;
private void stuff(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
throw new System.NotImplementedException();
}
but this is never hit as tested with breakpoints.
Am I missing something entirely, losing my mind or daft! Someone please give me a point in the right direction, if any of this makes any sense at all.
P.S another solution I though of would be to place an invisible button inside the listview and have that send a command to tell the vm that a selected item has been updated.
In cases like this where I want to do something when the property on an item inside the collection changes, I usually hook up a PropertyChanged event in the CollectionChanged event
Here's a code example :
public MyViewModel()
{
// Setup Collection, with a CollectionChanged event
Components = new ObservableCollection<Component>();
Components.CollectionChanged += Components_CollectionChanged;
}
// In the CollectionChanged event (items getting added or removed from collection),
// hook up the PropertyChanged event
void Components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(MyType item in e.NewItems)
item.PropertyChanged += Component_PropertyChanged;
if (e.OldItems != null)
foreach(MyType item in e.OldItems)
item.PropertyChanged -= Component_PropertyChanged;
}
// In the PropertyChanged event, run some code if SelectedComponent property changed
void Component_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedComponent")
DoWork();
}
Note that we are using two separate events here.
CollectionChanged event runs when the collection itself changes. This means it gets set to a new collection, or item(s) get added or removed from the collection.
This captures newly added items and hooks up the PropertyChanged
handler, or detaches the PropertyChanged handler for items being
removed from the collection.
PropertyChanged event runs when a property changes and fires the event. You'll use this event to run code when the SelectedComponent property on any item in the collection changes
Well, components hasn't changed. I'm not sure if you set up your handler correctly, but even if you did, a CollectionChanged event is only fired, if the collection changed (item added or removed).
Lets say you have a collection of cars and all are red.
You pick one car and set it's color to blue.
The collection has not changed. It's still the very same cars. No car is missing, no car was added.
You probably want to attach yourself to the handlers of all cars instead of the handler of the collection.
So to sum it up:
ObservableCollection<T> isn't getting notified of change in property of <T>
That's true and it's by design.
When adding items to the collection you want to "hook up" the events you are interested in for those items, then you can act as needed. As mentioned above, the collection isn't changing it is the individual items that are changing.
One thing you could do is extend the ObservableCollection class and override the functionality so that when the collection is changed, an item is either added or removed, you then go through the items in the collection and "hook up" the events you're interested in. One thing to note is you may have to go through the collection and remove the event handlers and "hook up" them again in order to stop getting multiple event handlers being set for the items in the list.
ObservableCollection<T> does not send notifications about property changes of the elements. However, the System.ComponentModel namespace contains another collection, which supports that: you can consider to use BindingList<T> instead, which is also supported by WPF (elements must implement INotifyPropertyChanged).
Please note though that BindingList<T> scales poorly and its performance starts to decline above hundreds and thousands of elements as it always searches for the changed element sequentially in order to return the element index in its ListChanged event.
I need to check some conditions and then decide to add the new Item to my collection or not, is there anyway i can prevent adding in my CollectionChanged event or is it too late at the point? actually i can modify the new Item but can not remove it from the NewItems collection due to runtime exception:
protected void MyFilter(object sender, NotifyCollectionChangedEventArgs e)
{
f (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (Item item in e.NewItems)
{
if (!Item.CanBeAdded())
{
//Prevent adding the Item !
}
}
}
}
Well the problem here is it's already added in the collection. The only way to do it if I follow this is removing it from the collection then. But that would retrigger the MyyFilter subscription you created.
If this collection is bound on a grid or anywhere where you can create an item from the UI, then i suggest you create a custom observable collection.
Basic code would be like this.
public class MyObservableCollection<T> : ICollection<T>
, INotifyCollectionChanged
, INotifyPropertyChanged
{
}
Implement your validation logic on the Add method of ICollection.
if you want to, just go ahead and copy this guys implementation.
"Occurs when an item is added, removed, changed, moved, or the entire list is refreshed." This is written in MSDN, talking about CollectionChanged event... but.. what does it mean with "changed"??
I have an observableCollection. It's populated from db and it is binded in my view (i'm working with mvvm pattern).
So, I think: "well, from my view I edit my fields, binded with the observableCollection". With the debug, I see the fields updated, but... the event is not fired... why?
What does the msdn mean with "changed"? and, if this way is wrong, how can I trigger my action when the value of the observablecollection are updated in the way I explained before?
In this context changed means replacing the item at a certain index with another item, not mutating an existing item. Mutating an item in the collection is not a change in the collection, nor does it have a way of observing such changes in the general case.
You're confusing the INotifyCollectionChanged interface with the INotifyPropertyChanged interface. It sounds like you want to know when any property of any item in the collection changes. In order to make that happen, you will need to implement the INotifyPropertyChanged interface in your data type class and attach a handler to the INotifyPropertyChanged.PropertyChanged event:
item.PropertyChanged += Item_PropertyChanged;
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// The 'e.PropertyName' property changed
}
You can improve this further by extending the ObservableCollection<YourDataType> class and adding this code into that class:
public new void Add(T item)
{
item.PropertyChanged += Item_PropertyChanged;
base.Add(item);
}
...
public new bool Remove(T item)
{
if (item == null) return false;
item.PropertyChanged -= Item_PropertyChanged;
return base.Remove(item);
}
There are also other methods that you should override in this way, but once you have, you can just let this class do all of the work for you... even better if you put it into some kind of generic base class. If you make that class also implement the INotifyPropertyChanged interface, then you can also 'forward' all property changes for convenience:
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged(e.PropertyName);
}
When I'm binding, say a Label to a string, I define the string this way:
private string _lbl;
public string Lbl
{
get
{
return = _lbl;
}
set
{
_lbl=value;
OnPropertyChanged("Lbl");
}
}
With the INotifyPropertyChanged interface implemented in my class.
Should I define the same way an ObservableCollection or I just could leave it this way?
public ObservableCollection<File> myFiles {get; set;}
As a general rule, I tend to define ObservableCollections like this:
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items
{
get { return _items ?? (_items = new ObservableCollection<Item>()); }
}
This is called "Lazy initialization", where the ObservableCollection is only instantiated where it is first accessed.
This is a good way to ensure your Collection Will Never Be Null
Notice that it does not have a setter, because an ObservableCollection is not something that you usually assign to. Instead, if you want to completely replace items, do:
Items.Clear();
//... Add all the new items
This avoids WPF having to rebind all the CollectionChanged events and stuff in order to listen to and react to items added / removed from the collection. You only have 1 instance of the collection, forever. Whatever items you place on it, the collection remains the same.
This is not to be confused with the PropertyChange notification inside the ITEMS of the collection. WPF handles these concepts separately, because property changes are notified by ITEMS, but Collection changes (Item added or removed) are notified by the Collection itself.
If the myFiles property can change, then yes, you should raise the PropertyChanged event. If not (that is, if it's got no setter, or it has a private setter that is only set once, e.g. in the constructor), then you don't need to raise the event. The collection itself will raise its own PropertyChanged and CollectionChanged events, but the object that contains the collection must raise PropertyChanged if the property that contains the collection changes.
We have a WPF application, let’s say I have a Department object, in it I have an ObservableCollection of Courses, inside each Course I have an ObservableCollection of Teachers, and inside each Teacher I have an ObservableCollection of Student:
Department.cs
---- ObservableCollection Courses
Course.cs
---- ObservableCollection Teachers
Teacher.cs
---- ObservableCollection Students
Student.cs
---- Name, Age, etc
These 4 classes all implemented INotifyPropertyChanged, they are working fine by themselves, what I want to achieve is whenever anything inside the Department object graph is changed, whether it was a student’s age get changed, or a new course added or a teacher removed, anything at all, I want to know about it, ideally via an event like DepartmentChanged (and ideally this only raise once), so I can call some other long running action.
I have a hard time coming up with a clean way to do it. I understand that INotifyPropertyChanged only track property change of a class and ObservationCollection has no idea if one of its items has changed something.
So far I tried nested loop like (pseudo code):
foreach (var course in d.Courses){
foreach (var teacher in course.Teachers){
foreach (var student in teacher.Students){
((INotifyPropertyChanged)student).PropertyChanged += ...
}
teacher.Students.CollectionChanged += ...
}
}
But as can imagine, this created lots of PropertyChanged events since every property subscribe to that, and the action I wanted to do in Department ended up being executed many many times, which is bad.
I then tried using a timer (each of our class has an IsDirty() to determine if the object has changed) with interval set to 500:
if(department != null && department.IsDirty()){
//call some long running Action here
}
While this seems to work, somehow I feel this approach is wrong, I'd really like to avoid timer if possible, I would love to hear other suggestions and ideas!
Thanks,
This is just a quick idea, without a timer, can you implement IsDirty event on the Course, & Teacher classes?
For example, Teacher, managers on PropertyChanged internally (subscribes/unsubscribes properly when Student collection changes).
When any of its students have a valid change, all it has to do is raise IsDirty event, which all have student obj as a param.
Course will listen and capture teacher.IsDirty event, and will raise its own course.IsDirty where CourseEventArgs = new CourseEventArgs will have a Course, Teacher with Student that has dirty fields.
In the Department class you listen to Course IsDirty event and parse the args...
This idea, seems to do the same thing that you have, but not really, because it handles any PropertyChanged only on the lowest level then takes use of your own events to bubble necessary info to the top. Which separates top classes from Student's PropertyChanged behavioral implementations as well as extensive chatter..
Also if on the student level, you make changes (through UI, and post them, click save or whatever) then you can control when IsDirty is being raised, maybe only once.
I think you'll still have to use an IsDrity bit and INotifyPropertyChanged at the individual object level, but why don't you extend ObservableCollection<T> and override a few basic methods to handle registering for the object property changes? So for example, create something like:
public class ChangeTrackerCollection<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
foreach (var item in this)
UnregisterItemEvents(item);
base.ClearItems();
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
RegisterItemEvents(item);
}
protected override void RemoveItem(int index)
{
UnregisterItemEvents(this[index]);
base.RemoveItem(index);
}
private void RegisterItemEvents(T item)
{
item.PropertyChanged += this.OnItemPropertyChanged;
}
private void UnregisterItemEvents(T item)
{
item.PropertyChanged -= this.OnItemPropertyChanged;
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//TODO: raise event to parent object to notify that there was a change...
}
}