Implementing CollectionChanged - c#

I have added CollectionChanged eventhandler(onCollectionChanged) to one of the ObservableCollection property.
I have found out that onCollectionChanged method gets invoked only in case of add items or remove items to the collection, but not in the case of collection item gets edited.
I would like to know how to send the list/collection of newly added, removed and edited items in a single collection.
Thanks.

You have to add a PropertyChanged listener to each item (which must implement INotifyPropertyChanged) to get notification about editing objects in a observable list.
public ObservableCollection<Item> Names { get; set; }
public List<Item> ModifiedItems { get; set; }
public ViewModel()
{
this.ModifiedItems = new List<Item>();
this.Names = new ObservableCollection<Item>();
this.Names.CollectionChanged += this.OnCollectionChanged;
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(Item newItem in e.NewItems)
{
ModifiedItems.Add(newItem);
//Add listener for each item on PropertyChanged event
newItem.PropertyChanged += this.OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach(Item oldItem in e.OldItems)
{
ModifiedItems.Add(oldItem);
oldItem.PropertyChanged -= this.OnItemPropertyChanged;
}
}
}
void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Item item = sender as Item;
if(item != null)
ModifiedItems.Add(item);
}
Maybe you have to check if some item is already in the ModifedItems-List (with List's method Contains(object obj)) and only add a new item if the result of that method is false.
The class Item must implement INotifyPropertyChanged. See this example to know how. As Robert Rossney said you can also make that with IEditableObject - if you have that requirement.

An ItemsControl listens to CollectionChanged to manage the display of the collection of items it's presenting on the screen. A ContentControl listens to PropertyChanged to manage the display of the specific item that it's presenting on the screen. It's pretty easy to keep the two concepts separate in your mind once you understand this.
Tracking whether or not an item is edited isn't something either of these interfaces does. Property changes aren't edits - that is, they don't necessarily represent some kind of user-initiated change to the state of the object. For instance, an object might have an ElapsedTime property that's being continuously updated by a timer; the UI needs to be notified of these property-change events, but they certainly don't represent changes in the object's underlying data.
The standard way to track whether or not an object is edited is to first make that object implement IEditableObject. You can then, internally to the object's class, decide what changes constitute an edit (i.e. require you to call BeginEdit) and what changes don't. You can then implement a boolean IsDirty property that gets set when BeginEdit is called and cleared when EndEdit or CancelEdit is called. (I really don't understand why that property isn't part of IEditableObject; I haven't yet implemented an editable object that didn't require it.)
Of course, there's no need to implement that second level of abstraction if you don't need it - you can certainly listen PropertyChanged event and just assume that the object has been edited if it gets raised. It really depends on your requirements.

My edit to 'this answer' is rejected!
So I put my edit here:
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(Item newItem in e.NewItems)
{
ModifiedItems.Add(newItem);
//Add listener for each item on PropertyChanged event
if (e.Action == NotifyCollectionChangedAction.Add)
newItem.PropertyChanged += this.ListTagInfo_PropertyChanged;
else if (e.Action == NotifyCollectionChangedAction.Remove)
newItem.PropertyChanged -= this.ListTagInfo_PropertyChanged;
}
}
// MSDN: OldItems:Gets the list of items affected by a Replace, Remove, or Move action.
//if (e.OldItems != null) <--- removed
}

I think that populating the ObservableCollection with items that implement INotifyPropertyChanged will cause the CollectionChanged event to fire when an item raises its PropertyChanged notification.
On second thought, I think you need to use BindingList<T> to get individual item changes to propagate in this way out-of-the-box.
Otherwise, you'll need to manually subscribe to each item's change notifications and raise the CollectionChanged event. Note that if you're creating your own, derived ObservableCollection<T>, you'll have to subscribe at instantiation and on Add() and Insert(), and unsubscribe on Remove(), RemoveAt() and Clear(). Otherwise, you can subscribe to the CollectionChanged event and use the added and removed items from the event args to subscribe/unsubscribe.

INotifyCollectionChanged is not one in the same with INotiftyPropertyChanged. Changing properties of underlying objects does not in any way suggest the collection has changed.
One way to achieve this behavior is to create a custom collection which will interrogate the object once added and register for the INotifyPropertyChanged.PropertyChanged event; it would then need to de-register appropriately when an item is removed.
One caveat with this approach is when your objects are nested N levels deep. To solve this you will need to essentially interrogate each property using reflection to determine if it is perhaps yet another collection implementing INotifyCollectionChanged or other container which will need to be traversed.
Here is a rudimentary un-tested example...
public class ObservableCollectionExt<T> : ObservableCollection<T>
{
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
protected override void SetItem(int index, T item)
{
base.SetItem(index, item);
if(item is INotifyPropertyChanged)
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
protected override void ClearItems()
{
for (int i = 0; i < this.Items.Count; i++)
DeRegisterINotifyPropertyChanged(this.IndexOf(this.Items[i]));
base.ClearItems();
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
RegisterINotifyPropertyChanged(item);
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
DeRegisterINotifyPropertyChanged(index);
}
private void RegisterINotifyPropertyChanged(T item)
{
if (item is INotifyPropertyChanged)
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void DeRegisterINotifyPropertyChanged(int index)
{
if (this.Items[index] is INotifyPropertyChanged)
(this.Items[index] as INotifyPropertyChanged).PropertyChanged -= OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T item = (T)sender;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, item));
}
}

in winforms, BindingList is standard practice. in WPF & Silverlight, you are usually stuck working with ObservableCollection and need to listen for PropertyChanged on each item

Use the following code:
-my Model:
public class IceCream: INotifyPropertyChanged
{
private int liczba;
public int Liczba
{
get { return liczba; }
set { liczba = value;
Zmiana("Liczba");
}
}
public IceCream(){}
//in the same class implement the below-it will be responsible for track a changes
public event PropertyChangedEventHandler PropertyChanged;
private void Zmiana(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And in my class PersonList implement method responsible for active increasing the value of one after button click in AppBarControl
async private void Add_Click(object sender, RoutedEventArgs e)
{
List<IceCream> items = new List<IceCream>();
foreach (IceCream item in IceCreamList.SelectedItems)
{
int i=Flavors.IndexOf(item);
Flavors[i].Liczba =item.Liczba+ 1;
//Flavors.Remove(item);
//item.Liczba += 1;
// items.Add(item);
// Flavors.Add(item);
}
MessageDialog d = new MessageDialog("Zwiększono liczbę o jeden");
d.Content = "Zwiększono liczbę o jeden";
await d.ShowAsync();
IceCreamList.SelectedIndex = -1;
}
}
I hope that it will be useful for someone to
Note that:
private ObservableCollection<IceCream> Flavors;
-

The easiest solution I found to this limitation, is to remove the item using RemoveAt(index) then add the modified item on the same index using InsertAt(index) and thus the ObservableCollection will notify the View.

Related

ObservableCollection<T> isn't getting notified of change in property of <T>

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.

Notify property in another class when property has changed?

I have a class which is a collection (MapAdapter) and takes a collection as a constructor parameter .MapAdater adds a Map object on CollectionChanged:
public MapAdapter(UndoRedoCollection undoRedoCollection)
{
this.undoRedoCollection = undoRedoCollection;
this.undoRedoCollection.CollectionChanged += this.OnCollectionChanged;
}
This is my collection changed event where the Map objects are added:
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
GetMapRules(TurbineUndoRedoCollection);
}
private void GetMapRules(UndoRedoCollection undoRedoCollection)
{
foreach (var item in undoRedoCollection)
{
this.Add(new Map(item));
}
}
I have a property on MapAdapter which determines whether the layer is visible on a map:
public bool IsLayerVisible
{
get
{
return this.isLayerVisible;
}
set
{
this.isLayerVisible = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("IsLayerVisible"));
}
}
I have a similar property on my Map class. I need to listen to the IsLayerVisible Property on my MapAdapter and change IsLayerVisible on each of my maps within the MapAdapter collection. The reason being is that I have a control which draws an image and is passed in a Map, it knows nothing of the MapAdater therefore I cannot control when & when not to draw based on visibility.
If the title of your post is provides any clues as to what you're trying to do then all you need to do is implement the INotifyPropertyChanged interface and fire the PropertyChanged event when the value of your property changes.
Then, modify your Map's constructor to accept a MapAdapter instance. You don't need to hold a reference to the MapAdapter within Map, just use it to wire up the event handler for the PropertyChanged event against your MapAdapter.
Here is an example of what the Map class might look like...
public class Map
{
public Map(object item, MapAdapter adapter)
{
...
adapter.PropertyChanged += this.AdapterPropertyChanged;
}
private void AdapterPropertyChanged(object sender, PropertyChangedEventArg e)
{
if (e.PropertyName == "IsLayerVisible")
{
// Do something
}
}
}
And then update your GetMapRules method like so...
private void GetMapRules(UndoRedoCollection undoRedoCollection)
{
foreach (var item in undoRedoCollection)
{
this.Add(new Map(item, this));
}
}
}
Look up pub-sub or Event Aggregator patterns.
Specifically, Prism has an implementation that may be of interest.
This link has a standalone example of this pattern that may be instructive as well.

Prevent adding the new Item on ObservableCollection.CollectionChanged 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.

ObservableCollection: raising event when updating values

"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);
}

Implement AddRange on ObservableCollection with proper support for DataBinding

I would like my own descendant of ObservableCollection to support AddRange method.
Here is what I currently have:
public class ObservableCollectionPlus<T> : ObservableCollection<T>
{
public void InsertRange(IEnumerable<T> items)
{
this.CheckReentrancy();
foreach (var item in items) Items.Add(item);
var type = NotifyCollectionChangedAction.Reset;
var colChanged = new NotifyCollectionChangedEventArgs(type);
var countChanged = new PropertyChangedEventArgs("Count");
OnPropertyChanged(countChanged);
OnCollectionChanged(colChanged);
}
}
I don't have much knowledge of what's exactly going on here and why are these events get raised. This is a solutiom that I've assembled after doing some research on google and stackoverflow.
Now, if I bind an instance of my class to say LongListSelector then, after dynamically adding items via InsertRange to ObservableCollectionPlus, a binded LongListSelector's scroll position is sent to it's top.
If I add items in this standard way: foreach (var item in items) collection.Add(item); then LongListSelector's position does not get shifted. But of course this way I get DataBinding notifications overhead in which is undesired.
Apparently, something is done wrong in my current solution. How can I implement InsertRange that will behave exactly like foreach (var item in items) collection.Add(item); but will only fire DataBinding notification once and will not do strange things to binded object's scroll position?
It could be because your sending the NotifyCollectionChangedAction.Reset notification, maybe just the NotifyCollectionChangedAction.Add will work, Maybe :)
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
public void AddRange(IEnumerable<T> collection)
{
foreach (var i in collection)
{
Items.Add(i);
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));
}
}
I've used this in a project recently...
public class RangeObservableCollection<T> : ObservableCollection<T>
{
private bool _suppressNotification = false;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable<T> list)
{
if (list == null)
throw new ArgumentNullException("list");
_suppressNotification = true;
foreach (T item in list)
{
Add(item);
}
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
The problem you are experiencing with DataBinding might be connected to the fact that you don't raise PropertyChanged for indexer (property name "Item[]") as it happens in ObservableCollection according to the source code.
You can also have a look at a nice implementation of ObservableRangeCollection by James Montemagno here on GitHub, it inherits from ObservableColection and includes AddRange and ReplaceRange methods with all PropertyChaged and CollectionChanged notifications needed for DataBinding.
It took me ages, the trouble was always with the arguments to pass to the NotifyCollectionChangedEventArgs ctor.
There are many different ctors that take different arguments, depending on the action. The following seems to finally work for me:
https://github.com/lolluslollus/Utilz/blob/master/Utilz/SwitchableObservableCollection.cs

Categories

Resources