ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
Does anyone know why this code doesn't work:
public class CollectionViewModel : ViewModelBase {
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
set
{
_contentList = value;
RaisePropertyChanged("ContentList");
//I want to be notified here when something changes..?
//debugger doesn't stop here when IsRowChecked is toggled
}
}
}
public class EntityViewModel : ViewModelBase
{
private bool _isRowChecked;
public bool IsRowChecked
{
get { return _isRowChecked; }
set { _isRowChecked = value; RaisePropertyChanged("IsRowChecked"); }
}
}
ViewModelBase
containts everything for RaisePropertyChanged
etc. and it's working for everything else except this problem..
c# observablecollection inotifypropertychanged
add a comment |
Does anyone know why this code doesn't work:
public class CollectionViewModel : ViewModelBase {
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
set
{
_contentList = value;
RaisePropertyChanged("ContentList");
//I want to be notified here when something changes..?
//debugger doesn't stop here when IsRowChecked is toggled
}
}
}
public class EntityViewModel : ViewModelBase
{
private bool _isRowChecked;
public bool IsRowChecked
{
get { return _isRowChecked; }
set { _isRowChecked = value; RaisePropertyChanged("IsRowChecked"); }
}
}
ViewModelBase
containts everything for RaisePropertyChanged
etc. and it's working for everything else except this problem..
c# observablecollection inotifypropertychanged
add a comment |
Does anyone know why this code doesn't work:
public class CollectionViewModel : ViewModelBase {
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
set
{
_contentList = value;
RaisePropertyChanged("ContentList");
//I want to be notified here when something changes..?
//debugger doesn't stop here when IsRowChecked is toggled
}
}
}
public class EntityViewModel : ViewModelBase
{
private bool _isRowChecked;
public bool IsRowChecked
{
get { return _isRowChecked; }
set { _isRowChecked = value; RaisePropertyChanged("IsRowChecked"); }
}
}
ViewModelBase
containts everything for RaisePropertyChanged
etc. and it's working for everything else except this problem..
c# observablecollection inotifypropertychanged
Does anyone know why this code doesn't work:
public class CollectionViewModel : ViewModelBase {
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
set
{
_contentList = value;
RaisePropertyChanged("ContentList");
//I want to be notified here when something changes..?
//debugger doesn't stop here when IsRowChecked is toggled
}
}
}
public class EntityViewModel : ViewModelBase
{
private bool _isRowChecked;
public bool IsRowChecked
{
get { return _isRowChecked; }
set { _isRowChecked = value; RaisePropertyChanged("IsRowChecked"); }
}
}
ViewModelBase
containts everything for RaisePropertyChanged
etc. and it's working for everything else except this problem..
c# observablecollection inotifypropertychanged
c# observablecollection inotifypropertychanged
edited Mar 1 '18 at 16:58
Robert Harvey♦
149k33276420
149k33276420
asked Sep 15 '09 at 14:17
Joseph jun. MelettukunnelJoseph jun. Melettukunnel
2,869135485
2,869135485
add a comment |
add a comment |
16 Answers
16
active
oldest
votes
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...)
30
note that if you don't want to implement the event management yourself, you can use a BindingList<EntityViewModel> in place of ObservableCollection<EntityViewModel>. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged.
– mjeanes
Sep 15 '09 at 15:35
14
Doesn't this all depend on your understanding of the termchanged
? This could mean that a property of one of the elements in the collection has changed (which is how I think you are interpreting it) or it could mean that one of the elements of the collection has been changed by replacing it with a different instance (this is my interpretation). Not totally convinced though - will have to look into it further.
– belugabob
Jul 6 '11 at 11:45
9
What happen if I invoke_contentList.Clear()
? No one will unsubscribe fromPropertyChanged
!
– Paolo Moretti
Aug 3 '12 at 11:00
2
@Paolo: That's right,ContentCollectionChanged
only handles Add/Remove, and not Replace/Reset. I'll try to edit and fix the post. The way simon does it in his answer is correct.
– Mike Fuchs
Mar 11 '13 at 12:55
1
See also stackoverflow.com/a/4588586/638977
– Behzad Ebrahimi
Apr 2 '16 at 7:16
|
show 9 more comments
Here is a drop-in class that sub-classes ObservableCollection and actually raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged
.
The benefit here is that you can data bind to this class and all of your bindings will update with changes to your item properties.
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
1
might be worth short circuiting if e.Action == move?
– jk.
Jul 29 '11 at 15:56
4
I've had cause to implement something similar myself, however rather than use NotifyCollectionChangedAction.Reset I instead used .Replace: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item, IndexOf(item)).
– Chris
Apr 29 '13 at 15:34
2
Awesome solution to my problem - thank you! For those who created their ObservableCollection with a List, you may want to add a constructor that also itterates though all the items and adds PropertyChanged.
– Gavin
Jun 23 '13 at 8:41
3
There's a potential memory leak here - A Reset event happens when the collection is significantly changed, e.g. on Clear. None of your INPC handlers will be unsubscribed when this happens.
– Charles Mager
Apr 16 '15 at 6:57
5
this is an OK implementation but it has one major issue - theNotifyCollectionChangedAction.Replace
is not a good idea, because then you can't distinguish between an item in fact being replaced or event caused by an item change. It gets much better when you definepublic event PropertyChangedEventHandler CollectionItemChanged;
and then inItemPropertyChanged
dothis.CollectionItemChanged?.Invoke(sender, e);
– Hristo Yankov
Dec 30 '16 at 17:06
|
show 8 more comments
This uses the above ideas but makes it a derived 'more sensitive' collection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace somethingelse
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
// this collection also reacts to changes in its components' properties
public ObservableCollectionEx() : base()
{
this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
}
void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(T 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 - note you must make it a 'reset' - dunno why
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
}
}
add a comment |
I've put together what I hope is a pretty robust solution, including some of the techniques in other answers. It is a new class derived from ObservableCollection<>
, which I'm calling FullyObservableCollection<>
It has the following features:
- It adds a new event,
ItemPropertyChanged
. I've deliberately kept this separate from the existingCollectionChanged
:
- To aid backward compatibility.
- So more relevant detail can be given in the new
ItemPropertyChangedEventArgs
that accompanies it: the originalPropertyChangedEventArgs
and the index within the collection.
- It replicates all the constructors from
ObservableCollection<>
. - It correctly handles the list being reset (
ObservableCollection<>.Clear()
), avoiding a possible memory leak. - It overrides the base class's
OnCollectionChanged()
, rather than a more resource-intensive subscription to theCollectionChanged
event.
Code
The complete .cs
file follows. Note that a few features of C# 6 have been used, but it should be fairly simple to backport it:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Utilities
{
public class FullyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection() : base()
{ }
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T typedSender = (T)sender;
int i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{ }
}
}
NUnit Tests
So you can check changes you might make (and see what I tested in the first place!), I've also included my NUnit test class. Obviously, the following code is not necessary just to use FullyObservableCollection<T>
in your project.
NB The test class uses BindableBase
from PRISM to implement INotifyPropertyChanged
. There is no dependency on PRISM from the main code.
using NUnit.Framework;
using Utilities;
using Microsoft.Practices.Prism.Mvvm;
using System.Collections.Specialized;
using System.Collections.Generic;
namespace Test_Utilities
{
[TestFixture]
public class Test_FullyObservableCollection : AssertionHelper
{
public class NotifyingTestClass : BindableBase
{
public int Id
{
get { return _Id; }
set { SetProperty(ref _Id, value); }
}
private int _Id;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
private string _Name;
}
FullyObservableCollection<NotifyingTestClass> TestCollection;
NotifyingTestClass Fred;
NotifyingTestClass Betty;
List<NotifyCollectionChangedEventArgs> CollectionEventList;
List<ItemPropertyChangedEventArgs> ItemEventList;
[SetUp]
public void Init()
{
Fred = new NotifyingTestClass() { Id = 1, Name = "Fred" };
Betty = new NotifyingTestClass() { Id = 4, Name = "Betty" };
TestCollection = new FullyObservableCollection<NotifyingTestClass>()
{
Fred,
new NotifyingTestClass() {Id = 2, Name = "Barney" },
new NotifyingTestClass() {Id = 3, Name = "Wilma" }
};
CollectionEventList = new List<NotifyCollectionChangedEventArgs>();
ItemEventList = new List<ItemPropertyChangedEventArgs>();
TestCollection.CollectionChanged += (o, e) => CollectionEventList.Add(e);
TestCollection.ItemPropertyChanged += (o, e) => ItemEventList.Add(e);
}
// Change existing member property: just ItemPropertyChanged(IPC) should fire
[Test]
public void DetectMemberPropertyChange()
{
TestCollection[0].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(1), "IPC count");
Expect(ItemEventList[0].PropertyName, Is.EqualTo(nameof(Fred.Id)), "Field Name");
Expect(ItemEventList[0].CollectionIndex, Is.EqualTo(0), "Collection Index");
}
// Add new member, change property: CollectionPropertyChanged (CPC) and IPC should fire
[Test]
public void DetectNewMemberPropertyChange()
{
TestCollection.Add(Betty);
Expect(TestCollection.Count, Is.EqualTo(4));
Expect(TestCollection[3].Name, Is.EqualTo("Betty"));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Add), "Action (add)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Betty), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
TestCollection[3].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Betty), "Collection Index dereference");
}
// Remove member, change property: CPC should fire for removel, neither CPC nor IPC should fire for change
[Test]
public void CeaseListentingWhenMemberRemoved()
{
TestCollection.Remove(Fred);
Expect(TestCollection.Count, Is.EqualTo(2));
Expect(TestCollection.IndexOf(Fred), Is.Negative);
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Remove), "Action (remove)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
// Move member in list, change property: CPC should fire for move, IPC should fire for change
[Test]
public void MoveMember()
{
TestCollection.Move(0, 1);
Expect(TestCollection.Count, Is.EqualTo(3));
Expect(TestCollection.IndexOf(Fred), Is.GreaterThan(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Move), "Action (move)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Fred), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count (post change)");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Fred), "Collection Index dereference");
}
// Clear list, chnage property: only CPC should fire for clear and neither for property change
[Test]
public void ClearList()
{
TestCollection.Clear();
Expect(TestCollection.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Reset), "Action (reset)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
}
}
I don't know what I am doing wrong, but this doesn't work for me. I'm binding my ListView to your collection but when I update the properties of the items inside, the ListView doesn't update, even tho' I can see all the events firing up. I'm also using the PRISM library...
– Renato Parreira
Feb 24 '17 at 15:20
@Renato, have you done anything with the new event?ListView
will respond toCollectionChanged
events because it knows about them.ItemPropertyChanged
is a non-standard addition, so you need to teach it about that. As a quick and dirty fix, you could try just firing theCollectionChanged
event as well as (or even instead of)ItemPropertyChanged
inOnItemPropertyChanged()
. I kept them separate for reasons stated in the answer, but for your use-case it might just do what you need.
– Bob Sammers
Mar 2 '17 at 18:13
add a comment |
ObservableCollection will not propagate individual item changes as CollectionChanged events. You will either need to subscribe to each event and forward it manually, or you can check out the BindingList[T] class, which will do this for you.
add a comment |
Added to TruelyObservableCollection event "ItemPropertyChanged":
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObservableCollectionTest
{
class Program
{
static void Main(string args)
{
// ATTN: Please note it's a "TrulyObservableCollection" that's instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
// REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });
Trades.CollectionChanged += Trades_CollectionChanged;
Trades.ItemPropertyChanged += PropertyChangedHandler;
Trades.RemoveAt(2);
Trades[0].Qty = 999;
Console.WriteLine("Hit any key to exit");
Console.ReadLine();
return;
}
static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
return;
}
static void Trades_CollectionChanged(object sender, EventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
return;
}
}
#region TrulyObservableCollection
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event PropertyChangedEventHandler ItemPropertyChanged;
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(sender, e);
}
}
}
#endregion
#region Sample entity
class Trade : INotifyPropertyChanged
{
protected string _Symbol;
protected int _Qty = 0;
protected DateTime _OrderPlaced = DateTime.Now;
public DateTime OrderPlaced
{
get { return _OrderPlaced; }
}
public string Symbol
{
get
{
return _Symbol;
}
set
{
_Symbol = value;
NotifyPropertyChanged("Symbol");
}
}
public int Qty
{
get
{
return _Qty;
}
set
{
_Qty = value;
NotifyPropertyChanged("Qty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
You could use PropertyChanged from ObservableCollection directly, since it implements INotifyPropertyChanged.
– Dieter Meemken
Jun 3 '16 at 14:23
add a comment |
I used Jack Kenyons answer to implement my own OC, but I'd like to point out one change i had to make to make it work. Instead of:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
I used this:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
It seems that the "e.NewItems" produces null if action is .Remove.
I think it needs further changes as well what if e.Action == replace
– jk.
Jul 29 '11 at 15:53
add a comment |
Just adding my 2 cents on this topic. Felt the TrulyObservableCollection required the two other constructors as found with ObservableCollection:
public TrulyObservableCollection()
: base()
{
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in collection)
item.PropertyChanged += ItemPropertyChanged;
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);
HookupCollectionChangedEvent();
}
private void HookupCollectionChangedEvent()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
}
add a comment |
I know that I'm too late for this party, but maybe - it will help to someone..
Here you can find my implementation of ObservableCollectionEx. It has some features:
- it supports everything from ObservableCollection
- it's thread safe
- it supports ItemPropertyChanged event (it raises each time when Item.PropertyChanged item is fired)
- it supports filters (so, you could create ObservableCollectionEx, pass another collection as Source to it, and Filter with simple predicate. Very useful in WPF, I use this feature a lot in my applications). Even more - filter tracks changes of items via INotifyPropertyChanged interface.
Of course, any comments are appreciated ;)
1
Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :)
– Alexander
Jul 7 '13 at 6:23
@Alexander you're very wellcome :)
– chopikadze
Jul 7 '13 at 12:02
@chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks
– Shax
Jan 11 '17 at 8:06
The link is dead.
– user2655904
Jul 31 '17 at 5:39
add a comment |
Simple solution for standard observablecollection that I've used:
DO NOT ADD to your property OR CHANGE it's inner items DIRECTLY, instead, create some temp collection like this
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
and add items or make changes to tmpList,
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
then pass it to your actual property by assignment.
ContentList=tmpList;
this will change whole property which causes notice the INotifyPropertyChanged as you need.
add a comment |
I try this solution, but only works for me like a RaisePropertyChange("SourceGroupeGridView") when collection changed, that fired for each item add or changed.
The problem is in:
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Reset this action make a complete rebind of all items in groupedgrid, is equivalent at RaisePropertyChanged. When you use it all groups of gridview refreshed.
IF you, only want to refresh in UI the group of the new item, you don't use Reset action, you will need simulate a Add action in itemproperty with something like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = this.IndexOf((T)sender);
this.RemoveAt(index);
this.Insert(index, (T)sender);
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
OnCollectionChanged(a);
}
Sorry by my english, and thanks for the base code :),
I hope this helps someone ^_^
Enjoi!!
add a comment |
Here's an extension method for the above solution...
public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
where T : INotifyPropertyChanged
{
var newList = new TrulyObservableCollection<T>();
if (list != null)
{
list.ForEach(o => newList.Add(o));
}
return newList;
}
You may want to explain the answer
– geedubb
Oct 23 '18 at 15:43
1
Here's a link that describes extension methods. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
– LawMan
Oct 23 '18 at 19:18
add a comment |
Instead of an ObservableCollection or TrulyObservableCollection, consider using a BindingList and calling the ResetBindings method.
For example:
private BindingList<TfsFile> _tfsFiles;
public BindingList<TfsFile> TfsFiles
{
get { return _tfsFiles; }
set
{
_tfsFiles = value;
NotifyPropertyChanged();
}
}
Given an event, such as a click your code would look like this:
foreach (var file in TfsFiles)
{
SelectedFile = file;
file.Name = "Different Text";
TfsFiles.ResetBindings();
}
My model looked like this:
namespace Models
{
public class TfsFile
{
public string ImagePath { get; set; }
public string FullPath { get; set; }
public string Name { get; set; }
public string Text { get; set; }
}
}
1
Good info on this method ofBindingList
, but there is a limitation to this approach that the other answers overcome: this technique relies on the value being changed in code and where a call toResetBindings()
can be added. Most of the other answers will work if the list's objects are altered through other means, such as unalterable code or from a binding to a second control.
– Bob Sammers
Mar 2 '17 at 18:44
add a comment |
If i know ObservableCollection make event only when we add/delete or move items in our collection. When we simly update some properties in collection items collection don`t signalize about it and UI will not be updated.
You can simly implement INotifyPropertyChange in your Model class.
And than when we update some propery in collection item it automatically will update UI.
public class Model:INotifyPropertyChange
{
//...
}
and than
public ObservableCollection<Model> {get; set;}
In my case i used ListView to Bind for this collection and in ItemTemplate set Binding to Model property and it work good.
Here is some snippet
Windows XAML :
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView
Margin="10"
BorderBrush="Black"
HorizontalAlignment="Center"
SelectedItem="{Binding SelectedPerson}"
ItemsSource="{Binding Persons}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
<Label Content="-"/>
<Label Content="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid
Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label
VerticalAlignment="Center"
Content="Name:"/>
<TextBox
Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Column="1"
Width="100"/>
<Label
VerticalAlignment="Center"
Grid.Row="1"
Content="Age:"/>
<TextBox
Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Row="1"
Grid.Column="1"
Width="100"/>
</Grid>
</Grid>
Model code example:
public class PersonModel:INotifyPropertyChanged
{
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public int Age
{
get => _age;
set
{
_age = value;
OnPropertyChanged();
}
}
private string _name;
private int _age;
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And ViewModel implementation:
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
Persons = new ObservableCollection<PersonModel>
{
new PersonModel
{
Name = "Jack",
Age = 30
},
new PersonModel
{
Name = "Jon",
Age = 23
},
new PersonModel
{
Name = "Max",
Age = 23
},
};
}
public ObservableCollection<PersonModel> Persons { get;}
public PersonModel SelectedPerson
{
get => _selectedPerson;
set
{
_selectedPerson = value;
OnPropertyChanged();
}
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private PersonModel _selectedPerson;
}
add a comment |
Here is my version of the implementation. It checks and throws an error, if the objects in list doesnt implement INotifyPropertyChanged, so can't forget that issue while developing. On the outside you use the ListItemChanged Event do determine whether the list or the list item itself has changed.
public class SpecialObservableCollection<T> : ObservableCollection<T>
{
public SpecialObservableCollection()
{
this.CollectionChanged += OnCollectionChanged;
}
void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
AddOrRemoveListToPropertyChanged(e.NewItems,true);
AddOrRemoveListToPropertyChanged(e.OldItems,false);
}
private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
{
if (list == null) { return; }
foreach (object item in list)
{
INotifyPropertyChanged o = item as INotifyPropertyChanged;
if (o != null)
{
if (add) { o.PropertyChanged += ListItemPropertyChanged; }
if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
}
else
{
throw new Exception("INotifyPropertyChanged is required");
}
}
}
void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnListItemChanged(this, e);
}
public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);
public event ListItemChangedEventHandler ListItemChanged;
private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
{
if (ListItemChanged != null) { this.ListItemChanged(this, e); }
}
}
add a comment |
Simple solution in 2 lines of code. Just use the copy constructor.
No need to write TrulyObservableCollection etc.
Example:
speakers.list[0].Status = "offline";
speakers.list[0] = new Speaker(speakers.list[0]);
Another method without copy constructor. You can use serialization.
speakers.list[0].Status = "offline";
//speakers.list[0] = new Speaker(speakers.list[0]);
var tmp = JsonConvert.SerializeObject(speakers.list[0]);
var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
speakers.list[0] = tmp2;
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f1427471%2fobservablecollection-not-noticing-when-item-in-it-changes-even-with-inotifyprop%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
16 Answers
16
active
oldest
votes
16 Answers
16
active
oldest
votes
active
oldest
votes
active
oldest
votes
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...)
30
note that if you don't want to implement the event management yourself, you can use a BindingList<EntityViewModel> in place of ObservableCollection<EntityViewModel>. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged.
– mjeanes
Sep 15 '09 at 15:35
14
Doesn't this all depend on your understanding of the termchanged
? This could mean that a property of one of the elements in the collection has changed (which is how I think you are interpreting it) or it could mean that one of the elements of the collection has been changed by replacing it with a different instance (this is my interpretation). Not totally convinced though - will have to look into it further.
– belugabob
Jul 6 '11 at 11:45
9
What happen if I invoke_contentList.Clear()
? No one will unsubscribe fromPropertyChanged
!
– Paolo Moretti
Aug 3 '12 at 11:00
2
@Paolo: That's right,ContentCollectionChanged
only handles Add/Remove, and not Replace/Reset. I'll try to edit and fix the post. The way simon does it in his answer is correct.
– Mike Fuchs
Mar 11 '13 at 12:55
1
See also stackoverflow.com/a/4588586/638977
– Behzad Ebrahimi
Apr 2 '16 at 7:16
|
show 9 more comments
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...)
30
note that if you don't want to implement the event management yourself, you can use a BindingList<EntityViewModel> in place of ObservableCollection<EntityViewModel>. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged.
– mjeanes
Sep 15 '09 at 15:35
14
Doesn't this all depend on your understanding of the termchanged
? This could mean that a property of one of the elements in the collection has changed (which is how I think you are interpreting it) or it could mean that one of the elements of the collection has been changed by replacing it with a different instance (this is my interpretation). Not totally convinced though - will have to look into it further.
– belugabob
Jul 6 '11 at 11:45
9
What happen if I invoke_contentList.Clear()
? No one will unsubscribe fromPropertyChanged
!
– Paolo Moretti
Aug 3 '12 at 11:00
2
@Paolo: That's right,ContentCollectionChanged
only handles Add/Remove, and not Replace/Reset. I'll try to edit and fix the post. The way simon does it in his answer is correct.
– Mike Fuchs
Mar 11 '13 at 12:55
1
See also stackoverflow.com/a/4588586/638977
– Behzad Ebrahimi
Apr 2 '16 at 7:16
|
show 9 more comments
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...)
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...)
edited Feb 9 '18 at 23:27
Robert Harvey♦
149k33276420
149k33276420
answered Sep 15 '09 at 14:21
Martin HarrisMartin Harris
24.5k57992
24.5k57992
30
note that if you don't want to implement the event management yourself, you can use a BindingList<EntityViewModel> in place of ObservableCollection<EntityViewModel>. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged.
– mjeanes
Sep 15 '09 at 15:35
14
Doesn't this all depend on your understanding of the termchanged
? This could mean that a property of one of the elements in the collection has changed (which is how I think you are interpreting it) or it could mean that one of the elements of the collection has been changed by replacing it with a different instance (this is my interpretation). Not totally convinced though - will have to look into it further.
– belugabob
Jul 6 '11 at 11:45
9
What happen if I invoke_contentList.Clear()
? No one will unsubscribe fromPropertyChanged
!
– Paolo Moretti
Aug 3 '12 at 11:00
2
@Paolo: That's right,ContentCollectionChanged
only handles Add/Remove, and not Replace/Reset. I'll try to edit and fix the post. The way simon does it in his answer is correct.
– Mike Fuchs
Mar 11 '13 at 12:55
1
See also stackoverflow.com/a/4588586/638977
– Behzad Ebrahimi
Apr 2 '16 at 7:16
|
show 9 more comments
30
note that if you don't want to implement the event management yourself, you can use a BindingList<EntityViewModel> in place of ObservableCollection<EntityViewModel>. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged.
– mjeanes
Sep 15 '09 at 15:35
14
Doesn't this all depend on your understanding of the termchanged
? This could mean that a property of one of the elements in the collection has changed (which is how I think you are interpreting it) or it could mean that one of the elements of the collection has been changed by replacing it with a different instance (this is my interpretation). Not totally convinced though - will have to look into it further.
– belugabob
Jul 6 '11 at 11:45
9
What happen if I invoke_contentList.Clear()
? No one will unsubscribe fromPropertyChanged
!
– Paolo Moretti
Aug 3 '12 at 11:00
2
@Paolo: That's right,ContentCollectionChanged
only handles Add/Remove, and not Replace/Reset. I'll try to edit and fix the post. The way simon does it in his answer is correct.
– Mike Fuchs
Mar 11 '13 at 12:55
1
See also stackoverflow.com/a/4588586/638977
– Behzad Ebrahimi
Apr 2 '16 at 7:16
30
30
note that if you don't want to implement the event management yourself, you can use a BindingList<EntityViewModel> in place of ObservableCollection<EntityViewModel>. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged.
– mjeanes
Sep 15 '09 at 15:35
note that if you don't want to implement the event management yourself, you can use a BindingList<EntityViewModel> in place of ObservableCollection<EntityViewModel>. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged.
– mjeanes
Sep 15 '09 at 15:35
14
14
Doesn't this all depend on your understanding of the term
changed
? This could mean that a property of one of the elements in the collection has changed (which is how I think you are interpreting it) or it could mean that one of the elements of the collection has been changed by replacing it with a different instance (this is my interpretation). Not totally convinced though - will have to look into it further.– belugabob
Jul 6 '11 at 11:45
Doesn't this all depend on your understanding of the term
changed
? This could mean that a property of one of the elements in the collection has changed (which is how I think you are interpreting it) or it could mean that one of the elements of the collection has been changed by replacing it with a different instance (this is my interpretation). Not totally convinced though - will have to look into it further.– belugabob
Jul 6 '11 at 11:45
9
9
What happen if I invoke
_contentList.Clear()
? No one will unsubscribe from PropertyChanged
!– Paolo Moretti
Aug 3 '12 at 11:00
What happen if I invoke
_contentList.Clear()
? No one will unsubscribe from PropertyChanged
!– Paolo Moretti
Aug 3 '12 at 11:00
2
2
@Paolo: That's right,
ContentCollectionChanged
only handles Add/Remove, and not Replace/Reset. I'll try to edit and fix the post. The way simon does it in his answer is correct.– Mike Fuchs
Mar 11 '13 at 12:55
@Paolo: That's right,
ContentCollectionChanged
only handles Add/Remove, and not Replace/Reset. I'll try to edit and fix the post. The way simon does it in his answer is correct.– Mike Fuchs
Mar 11 '13 at 12:55
1
1
See also stackoverflow.com/a/4588586/638977
– Behzad Ebrahimi
Apr 2 '16 at 7:16
See also stackoverflow.com/a/4588586/638977
– Behzad Ebrahimi
Apr 2 '16 at 7:16
|
show 9 more comments
Here is a drop-in class that sub-classes ObservableCollection and actually raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged
.
The benefit here is that you can data bind to this class and all of your bindings will update with changes to your item properties.
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
1
might be worth short circuiting if e.Action == move?
– jk.
Jul 29 '11 at 15:56
4
I've had cause to implement something similar myself, however rather than use NotifyCollectionChangedAction.Reset I instead used .Replace: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item, IndexOf(item)).
– Chris
Apr 29 '13 at 15:34
2
Awesome solution to my problem - thank you! For those who created their ObservableCollection with a List, you may want to add a constructor that also itterates though all the items and adds PropertyChanged.
– Gavin
Jun 23 '13 at 8:41
3
There's a potential memory leak here - A Reset event happens when the collection is significantly changed, e.g. on Clear. None of your INPC handlers will be unsubscribed when this happens.
– Charles Mager
Apr 16 '15 at 6:57
5
this is an OK implementation but it has one major issue - theNotifyCollectionChangedAction.Replace
is not a good idea, because then you can't distinguish between an item in fact being replaced or event caused by an item change. It gets much better when you definepublic event PropertyChangedEventHandler CollectionItemChanged;
and then inItemPropertyChanged
dothis.CollectionItemChanged?.Invoke(sender, e);
– Hristo Yankov
Dec 30 '16 at 17:06
|
show 8 more comments
Here is a drop-in class that sub-classes ObservableCollection and actually raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged
.
The benefit here is that you can data bind to this class and all of your bindings will update with changes to your item properties.
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
1
might be worth short circuiting if e.Action == move?
– jk.
Jul 29 '11 at 15:56
4
I've had cause to implement something similar myself, however rather than use NotifyCollectionChangedAction.Reset I instead used .Replace: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item, IndexOf(item)).
– Chris
Apr 29 '13 at 15:34
2
Awesome solution to my problem - thank you! For those who created their ObservableCollection with a List, you may want to add a constructor that also itterates though all the items and adds PropertyChanged.
– Gavin
Jun 23 '13 at 8:41
3
There's a potential memory leak here - A Reset event happens when the collection is significantly changed, e.g. on Clear. None of your INPC handlers will be unsubscribed when this happens.
– Charles Mager
Apr 16 '15 at 6:57
5
this is an OK implementation but it has one major issue - theNotifyCollectionChangedAction.Replace
is not a good idea, because then you can't distinguish between an item in fact being replaced or event caused by an item change. It gets much better when you definepublic event PropertyChangedEventHandler CollectionItemChanged;
and then inItemPropertyChanged
dothis.CollectionItemChanged?.Invoke(sender, e);
– Hristo Yankov
Dec 30 '16 at 17:06
|
show 8 more comments
Here is a drop-in class that sub-classes ObservableCollection and actually raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged
.
The benefit here is that you can data bind to this class and all of your bindings will update with changes to your item properties.
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
Here is a drop-in class that sub-classes ObservableCollection and actually raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged
.
The benefit here is that you can data bind to this class and all of your bindings will update with changes to your item properties.
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
edited Jul 31 '15 at 4:50
Mike de Klerk
6,63662857
6,63662857
answered Mar 10 '11 at 7:47


simonsimon
2,58021822
2,58021822
1
might be worth short circuiting if e.Action == move?
– jk.
Jul 29 '11 at 15:56
4
I've had cause to implement something similar myself, however rather than use NotifyCollectionChangedAction.Reset I instead used .Replace: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item, IndexOf(item)).
– Chris
Apr 29 '13 at 15:34
2
Awesome solution to my problem - thank you! For those who created their ObservableCollection with a List, you may want to add a constructor that also itterates though all the items and adds PropertyChanged.
– Gavin
Jun 23 '13 at 8:41
3
There's a potential memory leak here - A Reset event happens when the collection is significantly changed, e.g. on Clear. None of your INPC handlers will be unsubscribed when this happens.
– Charles Mager
Apr 16 '15 at 6:57
5
this is an OK implementation but it has one major issue - theNotifyCollectionChangedAction.Replace
is not a good idea, because then you can't distinguish between an item in fact being replaced or event caused by an item change. It gets much better when you definepublic event PropertyChangedEventHandler CollectionItemChanged;
and then inItemPropertyChanged
dothis.CollectionItemChanged?.Invoke(sender, e);
– Hristo Yankov
Dec 30 '16 at 17:06
|
show 8 more comments
1
might be worth short circuiting if e.Action == move?
– jk.
Jul 29 '11 at 15:56
4
I've had cause to implement something similar myself, however rather than use NotifyCollectionChangedAction.Reset I instead used .Replace: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item, IndexOf(item)).
– Chris
Apr 29 '13 at 15:34
2
Awesome solution to my problem - thank you! For those who created their ObservableCollection with a List, you may want to add a constructor that also itterates though all the items and adds PropertyChanged.
– Gavin
Jun 23 '13 at 8:41
3
There's a potential memory leak here - A Reset event happens when the collection is significantly changed, e.g. on Clear. None of your INPC handlers will be unsubscribed when this happens.
– Charles Mager
Apr 16 '15 at 6:57
5
this is an OK implementation but it has one major issue - theNotifyCollectionChangedAction.Replace
is not a good idea, because then you can't distinguish between an item in fact being replaced or event caused by an item change. It gets much better when you definepublic event PropertyChangedEventHandler CollectionItemChanged;
and then inItemPropertyChanged
dothis.CollectionItemChanged?.Invoke(sender, e);
– Hristo Yankov
Dec 30 '16 at 17:06
1
1
might be worth short circuiting if e.Action == move?
– jk.
Jul 29 '11 at 15:56
might be worth short circuiting if e.Action == move?
– jk.
Jul 29 '11 at 15:56
4
4
I've had cause to implement something similar myself, however rather than use NotifyCollectionChangedAction.Reset I instead used .Replace: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item, IndexOf(item)).
– Chris
Apr 29 '13 at 15:34
I've had cause to implement something similar myself, however rather than use NotifyCollectionChangedAction.Reset I instead used .Replace: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item, IndexOf(item)).
– Chris
Apr 29 '13 at 15:34
2
2
Awesome solution to my problem - thank you! For those who created their ObservableCollection with a List, you may want to add a constructor that also itterates though all the items and adds PropertyChanged.
– Gavin
Jun 23 '13 at 8:41
Awesome solution to my problem - thank you! For those who created their ObservableCollection with a List, you may want to add a constructor that also itterates though all the items and adds PropertyChanged.
– Gavin
Jun 23 '13 at 8:41
3
3
There's a potential memory leak here - A Reset event happens when the collection is significantly changed, e.g. on Clear. None of your INPC handlers will be unsubscribed when this happens.
– Charles Mager
Apr 16 '15 at 6:57
There's a potential memory leak here - A Reset event happens when the collection is significantly changed, e.g. on Clear. None of your INPC handlers will be unsubscribed when this happens.
– Charles Mager
Apr 16 '15 at 6:57
5
5
this is an OK implementation but it has one major issue - the
NotifyCollectionChangedAction.Replace
is not a good idea, because then you can't distinguish between an item in fact being replaced or event caused by an item change. It gets much better when you define public event PropertyChangedEventHandler CollectionItemChanged;
and then in ItemPropertyChanged
do this.CollectionItemChanged?.Invoke(sender, e);
– Hristo Yankov
Dec 30 '16 at 17:06
this is an OK implementation but it has one major issue - the
NotifyCollectionChangedAction.Replace
is not a good idea, because then you can't distinguish between an item in fact being replaced or event caused by an item change. It gets much better when you define public event PropertyChangedEventHandler CollectionItemChanged;
and then in ItemPropertyChanged
do this.CollectionItemChanged?.Invoke(sender, e);
– Hristo Yankov
Dec 30 '16 at 17:06
|
show 8 more comments
This uses the above ideas but makes it a derived 'more sensitive' collection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace somethingelse
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
// this collection also reacts to changes in its components' properties
public ObservableCollectionEx() : base()
{
this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
}
void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(T 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 - note you must make it a 'reset' - dunno why
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
}
}
add a comment |
This uses the above ideas but makes it a derived 'more sensitive' collection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace somethingelse
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
// this collection also reacts to changes in its components' properties
public ObservableCollectionEx() : base()
{
this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
}
void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(T 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 - note you must make it a 'reset' - dunno why
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
}
}
add a comment |
This uses the above ideas but makes it a derived 'more sensitive' collection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace somethingelse
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
// this collection also reacts to changes in its components' properties
public ObservableCollectionEx() : base()
{
this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
}
void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(T 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 - note you must make it a 'reset' - dunno why
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
}
}
This uses the above ideas but makes it a derived 'more sensitive' collection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace somethingelse
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
// this collection also reacts to changes in its components' properties
public ObservableCollectionEx() : base()
{
this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
}
void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(T 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 - note you must make it a 'reset' - dunno why
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
}
}
edited Jan 28 '15 at 20:09


Nick Miller
1,94712045
1,94712045
answered Nov 24 '10 at 7:03
Jack KenyonJack Kenyon
18112
18112
add a comment |
add a comment |
I've put together what I hope is a pretty robust solution, including some of the techniques in other answers. It is a new class derived from ObservableCollection<>
, which I'm calling FullyObservableCollection<>
It has the following features:
- It adds a new event,
ItemPropertyChanged
. I've deliberately kept this separate from the existingCollectionChanged
:
- To aid backward compatibility.
- So more relevant detail can be given in the new
ItemPropertyChangedEventArgs
that accompanies it: the originalPropertyChangedEventArgs
and the index within the collection.
- It replicates all the constructors from
ObservableCollection<>
. - It correctly handles the list being reset (
ObservableCollection<>.Clear()
), avoiding a possible memory leak. - It overrides the base class's
OnCollectionChanged()
, rather than a more resource-intensive subscription to theCollectionChanged
event.
Code
The complete .cs
file follows. Note that a few features of C# 6 have been used, but it should be fairly simple to backport it:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Utilities
{
public class FullyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection() : base()
{ }
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T typedSender = (T)sender;
int i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{ }
}
}
NUnit Tests
So you can check changes you might make (and see what I tested in the first place!), I've also included my NUnit test class. Obviously, the following code is not necessary just to use FullyObservableCollection<T>
in your project.
NB The test class uses BindableBase
from PRISM to implement INotifyPropertyChanged
. There is no dependency on PRISM from the main code.
using NUnit.Framework;
using Utilities;
using Microsoft.Practices.Prism.Mvvm;
using System.Collections.Specialized;
using System.Collections.Generic;
namespace Test_Utilities
{
[TestFixture]
public class Test_FullyObservableCollection : AssertionHelper
{
public class NotifyingTestClass : BindableBase
{
public int Id
{
get { return _Id; }
set { SetProperty(ref _Id, value); }
}
private int _Id;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
private string _Name;
}
FullyObservableCollection<NotifyingTestClass> TestCollection;
NotifyingTestClass Fred;
NotifyingTestClass Betty;
List<NotifyCollectionChangedEventArgs> CollectionEventList;
List<ItemPropertyChangedEventArgs> ItemEventList;
[SetUp]
public void Init()
{
Fred = new NotifyingTestClass() { Id = 1, Name = "Fred" };
Betty = new NotifyingTestClass() { Id = 4, Name = "Betty" };
TestCollection = new FullyObservableCollection<NotifyingTestClass>()
{
Fred,
new NotifyingTestClass() {Id = 2, Name = "Barney" },
new NotifyingTestClass() {Id = 3, Name = "Wilma" }
};
CollectionEventList = new List<NotifyCollectionChangedEventArgs>();
ItemEventList = new List<ItemPropertyChangedEventArgs>();
TestCollection.CollectionChanged += (o, e) => CollectionEventList.Add(e);
TestCollection.ItemPropertyChanged += (o, e) => ItemEventList.Add(e);
}
// Change existing member property: just ItemPropertyChanged(IPC) should fire
[Test]
public void DetectMemberPropertyChange()
{
TestCollection[0].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(1), "IPC count");
Expect(ItemEventList[0].PropertyName, Is.EqualTo(nameof(Fred.Id)), "Field Name");
Expect(ItemEventList[0].CollectionIndex, Is.EqualTo(0), "Collection Index");
}
// Add new member, change property: CollectionPropertyChanged (CPC) and IPC should fire
[Test]
public void DetectNewMemberPropertyChange()
{
TestCollection.Add(Betty);
Expect(TestCollection.Count, Is.EqualTo(4));
Expect(TestCollection[3].Name, Is.EqualTo("Betty"));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Add), "Action (add)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Betty), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
TestCollection[3].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Betty), "Collection Index dereference");
}
// Remove member, change property: CPC should fire for removel, neither CPC nor IPC should fire for change
[Test]
public void CeaseListentingWhenMemberRemoved()
{
TestCollection.Remove(Fred);
Expect(TestCollection.Count, Is.EqualTo(2));
Expect(TestCollection.IndexOf(Fred), Is.Negative);
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Remove), "Action (remove)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
// Move member in list, change property: CPC should fire for move, IPC should fire for change
[Test]
public void MoveMember()
{
TestCollection.Move(0, 1);
Expect(TestCollection.Count, Is.EqualTo(3));
Expect(TestCollection.IndexOf(Fred), Is.GreaterThan(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Move), "Action (move)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Fred), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count (post change)");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Fred), "Collection Index dereference");
}
// Clear list, chnage property: only CPC should fire for clear and neither for property change
[Test]
public void ClearList()
{
TestCollection.Clear();
Expect(TestCollection.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Reset), "Action (reset)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
}
}
I don't know what I am doing wrong, but this doesn't work for me. I'm binding my ListView to your collection but when I update the properties of the items inside, the ListView doesn't update, even tho' I can see all the events firing up. I'm also using the PRISM library...
– Renato Parreira
Feb 24 '17 at 15:20
@Renato, have you done anything with the new event?ListView
will respond toCollectionChanged
events because it knows about them.ItemPropertyChanged
is a non-standard addition, so you need to teach it about that. As a quick and dirty fix, you could try just firing theCollectionChanged
event as well as (or even instead of)ItemPropertyChanged
inOnItemPropertyChanged()
. I kept them separate for reasons stated in the answer, but for your use-case it might just do what you need.
– Bob Sammers
Mar 2 '17 at 18:13
add a comment |
I've put together what I hope is a pretty robust solution, including some of the techniques in other answers. It is a new class derived from ObservableCollection<>
, which I'm calling FullyObservableCollection<>
It has the following features:
- It adds a new event,
ItemPropertyChanged
. I've deliberately kept this separate from the existingCollectionChanged
:
- To aid backward compatibility.
- So more relevant detail can be given in the new
ItemPropertyChangedEventArgs
that accompanies it: the originalPropertyChangedEventArgs
and the index within the collection.
- It replicates all the constructors from
ObservableCollection<>
. - It correctly handles the list being reset (
ObservableCollection<>.Clear()
), avoiding a possible memory leak. - It overrides the base class's
OnCollectionChanged()
, rather than a more resource-intensive subscription to theCollectionChanged
event.
Code
The complete .cs
file follows. Note that a few features of C# 6 have been used, but it should be fairly simple to backport it:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Utilities
{
public class FullyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection() : base()
{ }
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T typedSender = (T)sender;
int i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{ }
}
}
NUnit Tests
So you can check changes you might make (and see what I tested in the first place!), I've also included my NUnit test class. Obviously, the following code is not necessary just to use FullyObservableCollection<T>
in your project.
NB The test class uses BindableBase
from PRISM to implement INotifyPropertyChanged
. There is no dependency on PRISM from the main code.
using NUnit.Framework;
using Utilities;
using Microsoft.Practices.Prism.Mvvm;
using System.Collections.Specialized;
using System.Collections.Generic;
namespace Test_Utilities
{
[TestFixture]
public class Test_FullyObservableCollection : AssertionHelper
{
public class NotifyingTestClass : BindableBase
{
public int Id
{
get { return _Id; }
set { SetProperty(ref _Id, value); }
}
private int _Id;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
private string _Name;
}
FullyObservableCollection<NotifyingTestClass> TestCollection;
NotifyingTestClass Fred;
NotifyingTestClass Betty;
List<NotifyCollectionChangedEventArgs> CollectionEventList;
List<ItemPropertyChangedEventArgs> ItemEventList;
[SetUp]
public void Init()
{
Fred = new NotifyingTestClass() { Id = 1, Name = "Fred" };
Betty = new NotifyingTestClass() { Id = 4, Name = "Betty" };
TestCollection = new FullyObservableCollection<NotifyingTestClass>()
{
Fred,
new NotifyingTestClass() {Id = 2, Name = "Barney" },
new NotifyingTestClass() {Id = 3, Name = "Wilma" }
};
CollectionEventList = new List<NotifyCollectionChangedEventArgs>();
ItemEventList = new List<ItemPropertyChangedEventArgs>();
TestCollection.CollectionChanged += (o, e) => CollectionEventList.Add(e);
TestCollection.ItemPropertyChanged += (o, e) => ItemEventList.Add(e);
}
// Change existing member property: just ItemPropertyChanged(IPC) should fire
[Test]
public void DetectMemberPropertyChange()
{
TestCollection[0].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(1), "IPC count");
Expect(ItemEventList[0].PropertyName, Is.EqualTo(nameof(Fred.Id)), "Field Name");
Expect(ItemEventList[0].CollectionIndex, Is.EqualTo(0), "Collection Index");
}
// Add new member, change property: CollectionPropertyChanged (CPC) and IPC should fire
[Test]
public void DetectNewMemberPropertyChange()
{
TestCollection.Add(Betty);
Expect(TestCollection.Count, Is.EqualTo(4));
Expect(TestCollection[3].Name, Is.EqualTo("Betty"));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Add), "Action (add)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Betty), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
TestCollection[3].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Betty), "Collection Index dereference");
}
// Remove member, change property: CPC should fire for removel, neither CPC nor IPC should fire for change
[Test]
public void CeaseListentingWhenMemberRemoved()
{
TestCollection.Remove(Fred);
Expect(TestCollection.Count, Is.EqualTo(2));
Expect(TestCollection.IndexOf(Fred), Is.Negative);
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Remove), "Action (remove)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
// Move member in list, change property: CPC should fire for move, IPC should fire for change
[Test]
public void MoveMember()
{
TestCollection.Move(0, 1);
Expect(TestCollection.Count, Is.EqualTo(3));
Expect(TestCollection.IndexOf(Fred), Is.GreaterThan(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Move), "Action (move)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Fred), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count (post change)");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Fred), "Collection Index dereference");
}
// Clear list, chnage property: only CPC should fire for clear and neither for property change
[Test]
public void ClearList()
{
TestCollection.Clear();
Expect(TestCollection.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Reset), "Action (reset)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
}
}
I don't know what I am doing wrong, but this doesn't work for me. I'm binding my ListView to your collection but when I update the properties of the items inside, the ListView doesn't update, even tho' I can see all the events firing up. I'm also using the PRISM library...
– Renato Parreira
Feb 24 '17 at 15:20
@Renato, have you done anything with the new event?ListView
will respond toCollectionChanged
events because it knows about them.ItemPropertyChanged
is a non-standard addition, so you need to teach it about that. As a quick and dirty fix, you could try just firing theCollectionChanged
event as well as (or even instead of)ItemPropertyChanged
inOnItemPropertyChanged()
. I kept them separate for reasons stated in the answer, but for your use-case it might just do what you need.
– Bob Sammers
Mar 2 '17 at 18:13
add a comment |
I've put together what I hope is a pretty robust solution, including some of the techniques in other answers. It is a new class derived from ObservableCollection<>
, which I'm calling FullyObservableCollection<>
It has the following features:
- It adds a new event,
ItemPropertyChanged
. I've deliberately kept this separate from the existingCollectionChanged
:
- To aid backward compatibility.
- So more relevant detail can be given in the new
ItemPropertyChangedEventArgs
that accompanies it: the originalPropertyChangedEventArgs
and the index within the collection.
- It replicates all the constructors from
ObservableCollection<>
. - It correctly handles the list being reset (
ObservableCollection<>.Clear()
), avoiding a possible memory leak. - It overrides the base class's
OnCollectionChanged()
, rather than a more resource-intensive subscription to theCollectionChanged
event.
Code
The complete .cs
file follows. Note that a few features of C# 6 have been used, but it should be fairly simple to backport it:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Utilities
{
public class FullyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection() : base()
{ }
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T typedSender = (T)sender;
int i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{ }
}
}
NUnit Tests
So you can check changes you might make (and see what I tested in the first place!), I've also included my NUnit test class. Obviously, the following code is not necessary just to use FullyObservableCollection<T>
in your project.
NB The test class uses BindableBase
from PRISM to implement INotifyPropertyChanged
. There is no dependency on PRISM from the main code.
using NUnit.Framework;
using Utilities;
using Microsoft.Practices.Prism.Mvvm;
using System.Collections.Specialized;
using System.Collections.Generic;
namespace Test_Utilities
{
[TestFixture]
public class Test_FullyObservableCollection : AssertionHelper
{
public class NotifyingTestClass : BindableBase
{
public int Id
{
get { return _Id; }
set { SetProperty(ref _Id, value); }
}
private int _Id;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
private string _Name;
}
FullyObservableCollection<NotifyingTestClass> TestCollection;
NotifyingTestClass Fred;
NotifyingTestClass Betty;
List<NotifyCollectionChangedEventArgs> CollectionEventList;
List<ItemPropertyChangedEventArgs> ItemEventList;
[SetUp]
public void Init()
{
Fred = new NotifyingTestClass() { Id = 1, Name = "Fred" };
Betty = new NotifyingTestClass() { Id = 4, Name = "Betty" };
TestCollection = new FullyObservableCollection<NotifyingTestClass>()
{
Fred,
new NotifyingTestClass() {Id = 2, Name = "Barney" },
new NotifyingTestClass() {Id = 3, Name = "Wilma" }
};
CollectionEventList = new List<NotifyCollectionChangedEventArgs>();
ItemEventList = new List<ItemPropertyChangedEventArgs>();
TestCollection.CollectionChanged += (o, e) => CollectionEventList.Add(e);
TestCollection.ItemPropertyChanged += (o, e) => ItemEventList.Add(e);
}
// Change existing member property: just ItemPropertyChanged(IPC) should fire
[Test]
public void DetectMemberPropertyChange()
{
TestCollection[0].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(1), "IPC count");
Expect(ItemEventList[0].PropertyName, Is.EqualTo(nameof(Fred.Id)), "Field Name");
Expect(ItemEventList[0].CollectionIndex, Is.EqualTo(0), "Collection Index");
}
// Add new member, change property: CollectionPropertyChanged (CPC) and IPC should fire
[Test]
public void DetectNewMemberPropertyChange()
{
TestCollection.Add(Betty);
Expect(TestCollection.Count, Is.EqualTo(4));
Expect(TestCollection[3].Name, Is.EqualTo("Betty"));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Add), "Action (add)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Betty), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
TestCollection[3].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Betty), "Collection Index dereference");
}
// Remove member, change property: CPC should fire for removel, neither CPC nor IPC should fire for change
[Test]
public void CeaseListentingWhenMemberRemoved()
{
TestCollection.Remove(Fred);
Expect(TestCollection.Count, Is.EqualTo(2));
Expect(TestCollection.IndexOf(Fred), Is.Negative);
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Remove), "Action (remove)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
// Move member in list, change property: CPC should fire for move, IPC should fire for change
[Test]
public void MoveMember()
{
TestCollection.Move(0, 1);
Expect(TestCollection.Count, Is.EqualTo(3));
Expect(TestCollection.IndexOf(Fred), Is.GreaterThan(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Move), "Action (move)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Fred), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count (post change)");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Fred), "Collection Index dereference");
}
// Clear list, chnage property: only CPC should fire for clear and neither for property change
[Test]
public void ClearList()
{
TestCollection.Clear();
Expect(TestCollection.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Reset), "Action (reset)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
}
}
I've put together what I hope is a pretty robust solution, including some of the techniques in other answers. It is a new class derived from ObservableCollection<>
, which I'm calling FullyObservableCollection<>
It has the following features:
- It adds a new event,
ItemPropertyChanged
. I've deliberately kept this separate from the existingCollectionChanged
:
- To aid backward compatibility.
- So more relevant detail can be given in the new
ItemPropertyChangedEventArgs
that accompanies it: the originalPropertyChangedEventArgs
and the index within the collection.
- It replicates all the constructors from
ObservableCollection<>
. - It correctly handles the list being reset (
ObservableCollection<>.Clear()
), avoiding a possible memory leak. - It overrides the base class's
OnCollectionChanged()
, rather than a more resource-intensive subscription to theCollectionChanged
event.
Code
The complete .cs
file follows. Note that a few features of C# 6 have been used, but it should be fairly simple to backport it:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Utilities
{
public class FullyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection() : base()
{ }
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T typedSender = (T)sender;
int i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{ }
}
}
NUnit Tests
So you can check changes you might make (and see what I tested in the first place!), I've also included my NUnit test class. Obviously, the following code is not necessary just to use FullyObservableCollection<T>
in your project.
NB The test class uses BindableBase
from PRISM to implement INotifyPropertyChanged
. There is no dependency on PRISM from the main code.
using NUnit.Framework;
using Utilities;
using Microsoft.Practices.Prism.Mvvm;
using System.Collections.Specialized;
using System.Collections.Generic;
namespace Test_Utilities
{
[TestFixture]
public class Test_FullyObservableCollection : AssertionHelper
{
public class NotifyingTestClass : BindableBase
{
public int Id
{
get { return _Id; }
set { SetProperty(ref _Id, value); }
}
private int _Id;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
private string _Name;
}
FullyObservableCollection<NotifyingTestClass> TestCollection;
NotifyingTestClass Fred;
NotifyingTestClass Betty;
List<NotifyCollectionChangedEventArgs> CollectionEventList;
List<ItemPropertyChangedEventArgs> ItemEventList;
[SetUp]
public void Init()
{
Fred = new NotifyingTestClass() { Id = 1, Name = "Fred" };
Betty = new NotifyingTestClass() { Id = 4, Name = "Betty" };
TestCollection = new FullyObservableCollection<NotifyingTestClass>()
{
Fred,
new NotifyingTestClass() {Id = 2, Name = "Barney" },
new NotifyingTestClass() {Id = 3, Name = "Wilma" }
};
CollectionEventList = new List<NotifyCollectionChangedEventArgs>();
ItemEventList = new List<ItemPropertyChangedEventArgs>();
TestCollection.CollectionChanged += (o, e) => CollectionEventList.Add(e);
TestCollection.ItemPropertyChanged += (o, e) => ItemEventList.Add(e);
}
// Change existing member property: just ItemPropertyChanged(IPC) should fire
[Test]
public void DetectMemberPropertyChange()
{
TestCollection[0].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(1), "IPC count");
Expect(ItemEventList[0].PropertyName, Is.EqualTo(nameof(Fred.Id)), "Field Name");
Expect(ItemEventList[0].CollectionIndex, Is.EqualTo(0), "Collection Index");
}
// Add new member, change property: CollectionPropertyChanged (CPC) and IPC should fire
[Test]
public void DetectNewMemberPropertyChange()
{
TestCollection.Add(Betty);
Expect(TestCollection.Count, Is.EqualTo(4));
Expect(TestCollection[3].Name, Is.EqualTo("Betty"));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Add), "Action (add)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Betty), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
TestCollection[3].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Betty), "Collection Index dereference");
}
// Remove member, change property: CPC should fire for removel, neither CPC nor IPC should fire for change
[Test]
public void CeaseListentingWhenMemberRemoved()
{
TestCollection.Remove(Fred);
Expect(TestCollection.Count, Is.EqualTo(2));
Expect(TestCollection.IndexOf(Fred), Is.Negative);
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Remove), "Action (remove)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
// Move member in list, change property: CPC should fire for move, IPC should fire for change
[Test]
public void MoveMember()
{
TestCollection.Move(0, 1);
Expect(TestCollection.Count, Is.EqualTo(3));
Expect(TestCollection.IndexOf(Fred), Is.GreaterThan(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Move), "Action (move)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Fred), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count (post change)");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Fred), "Collection Index dereference");
}
// Clear list, chnage property: only CPC should fire for clear and neither for property change
[Test]
public void ClearList()
{
TestCollection.Clear();
Expect(TestCollection.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Reset), "Action (reset)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
}
}
edited Aug 21 '18 at 13:19
answered Aug 14 '15 at 15:29
Bob SammersBob Sammers
1,7841623
1,7841623
I don't know what I am doing wrong, but this doesn't work for me. I'm binding my ListView to your collection but when I update the properties of the items inside, the ListView doesn't update, even tho' I can see all the events firing up. I'm also using the PRISM library...
– Renato Parreira
Feb 24 '17 at 15:20
@Renato, have you done anything with the new event?ListView
will respond toCollectionChanged
events because it knows about them.ItemPropertyChanged
is a non-standard addition, so you need to teach it about that. As a quick and dirty fix, you could try just firing theCollectionChanged
event as well as (or even instead of)ItemPropertyChanged
inOnItemPropertyChanged()
. I kept them separate for reasons stated in the answer, but for your use-case it might just do what you need.
– Bob Sammers
Mar 2 '17 at 18:13
add a comment |
I don't know what I am doing wrong, but this doesn't work for me. I'm binding my ListView to your collection but when I update the properties of the items inside, the ListView doesn't update, even tho' I can see all the events firing up. I'm also using the PRISM library...
– Renato Parreira
Feb 24 '17 at 15:20
@Renato, have you done anything with the new event?ListView
will respond toCollectionChanged
events because it knows about them.ItemPropertyChanged
is a non-standard addition, so you need to teach it about that. As a quick and dirty fix, you could try just firing theCollectionChanged
event as well as (or even instead of)ItemPropertyChanged
inOnItemPropertyChanged()
. I kept them separate for reasons stated in the answer, but for your use-case it might just do what you need.
– Bob Sammers
Mar 2 '17 at 18:13
I don't know what I am doing wrong, but this doesn't work for me. I'm binding my ListView to your collection but when I update the properties of the items inside, the ListView doesn't update, even tho' I can see all the events firing up. I'm also using the PRISM library...
– Renato Parreira
Feb 24 '17 at 15:20
I don't know what I am doing wrong, but this doesn't work for me. I'm binding my ListView to your collection but when I update the properties of the items inside, the ListView doesn't update, even tho' I can see all the events firing up. I'm also using the PRISM library...
– Renato Parreira
Feb 24 '17 at 15:20
@Renato, have you done anything with the new event?
ListView
will respond to CollectionChanged
events because it knows about them. ItemPropertyChanged
is a non-standard addition, so you need to teach it about that. As a quick and dirty fix, you could try just firing the CollectionChanged
event as well as (or even instead of) ItemPropertyChanged
in OnItemPropertyChanged()
. I kept them separate for reasons stated in the answer, but for your use-case it might just do what you need.– Bob Sammers
Mar 2 '17 at 18:13
@Renato, have you done anything with the new event?
ListView
will respond to CollectionChanged
events because it knows about them. ItemPropertyChanged
is a non-standard addition, so you need to teach it about that. As a quick and dirty fix, you could try just firing the CollectionChanged
event as well as (or even instead of) ItemPropertyChanged
in OnItemPropertyChanged()
. I kept them separate for reasons stated in the answer, but for your use-case it might just do what you need.– Bob Sammers
Mar 2 '17 at 18:13
add a comment |
ObservableCollection will not propagate individual item changes as CollectionChanged events. You will either need to subscribe to each event and forward it manually, or you can check out the BindingList[T] class, which will do this for you.
add a comment |
ObservableCollection will not propagate individual item changes as CollectionChanged events. You will either need to subscribe to each event and forward it manually, or you can check out the BindingList[T] class, which will do this for you.
add a comment |
ObservableCollection will not propagate individual item changes as CollectionChanged events. You will either need to subscribe to each event and forward it manually, or you can check out the BindingList[T] class, which will do this for you.
ObservableCollection will not propagate individual item changes as CollectionChanged events. You will either need to subscribe to each event and forward it manually, or you can check out the BindingList[T] class, which will do this for you.
answered Sep 15 '09 at 15:13
mjeanesmjeanes
1,35686
1,35686
add a comment |
add a comment |
Added to TruelyObservableCollection event "ItemPropertyChanged":
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObservableCollectionTest
{
class Program
{
static void Main(string args)
{
// ATTN: Please note it's a "TrulyObservableCollection" that's instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
// REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });
Trades.CollectionChanged += Trades_CollectionChanged;
Trades.ItemPropertyChanged += PropertyChangedHandler;
Trades.RemoveAt(2);
Trades[0].Qty = 999;
Console.WriteLine("Hit any key to exit");
Console.ReadLine();
return;
}
static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
return;
}
static void Trades_CollectionChanged(object sender, EventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
return;
}
}
#region TrulyObservableCollection
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event PropertyChangedEventHandler ItemPropertyChanged;
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(sender, e);
}
}
}
#endregion
#region Sample entity
class Trade : INotifyPropertyChanged
{
protected string _Symbol;
protected int _Qty = 0;
protected DateTime _OrderPlaced = DateTime.Now;
public DateTime OrderPlaced
{
get { return _OrderPlaced; }
}
public string Symbol
{
get
{
return _Symbol;
}
set
{
_Symbol = value;
NotifyPropertyChanged("Symbol");
}
}
public int Qty
{
get
{
return _Qty;
}
set
{
_Qty = value;
NotifyPropertyChanged("Qty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
You could use PropertyChanged from ObservableCollection directly, since it implements INotifyPropertyChanged.
– Dieter Meemken
Jun 3 '16 at 14:23
add a comment |
Added to TruelyObservableCollection event "ItemPropertyChanged":
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObservableCollectionTest
{
class Program
{
static void Main(string args)
{
// ATTN: Please note it's a "TrulyObservableCollection" that's instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
// REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });
Trades.CollectionChanged += Trades_CollectionChanged;
Trades.ItemPropertyChanged += PropertyChangedHandler;
Trades.RemoveAt(2);
Trades[0].Qty = 999;
Console.WriteLine("Hit any key to exit");
Console.ReadLine();
return;
}
static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
return;
}
static void Trades_CollectionChanged(object sender, EventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
return;
}
}
#region TrulyObservableCollection
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event PropertyChangedEventHandler ItemPropertyChanged;
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(sender, e);
}
}
}
#endregion
#region Sample entity
class Trade : INotifyPropertyChanged
{
protected string _Symbol;
protected int _Qty = 0;
protected DateTime _OrderPlaced = DateTime.Now;
public DateTime OrderPlaced
{
get { return _OrderPlaced; }
}
public string Symbol
{
get
{
return _Symbol;
}
set
{
_Symbol = value;
NotifyPropertyChanged("Symbol");
}
}
public int Qty
{
get
{
return _Qty;
}
set
{
_Qty = value;
NotifyPropertyChanged("Qty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
You could use PropertyChanged from ObservableCollection directly, since it implements INotifyPropertyChanged.
– Dieter Meemken
Jun 3 '16 at 14:23
add a comment |
Added to TruelyObservableCollection event "ItemPropertyChanged":
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObservableCollectionTest
{
class Program
{
static void Main(string args)
{
// ATTN: Please note it's a "TrulyObservableCollection" that's instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
// REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });
Trades.CollectionChanged += Trades_CollectionChanged;
Trades.ItemPropertyChanged += PropertyChangedHandler;
Trades.RemoveAt(2);
Trades[0].Qty = 999;
Console.WriteLine("Hit any key to exit");
Console.ReadLine();
return;
}
static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
return;
}
static void Trades_CollectionChanged(object sender, EventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
return;
}
}
#region TrulyObservableCollection
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event PropertyChangedEventHandler ItemPropertyChanged;
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(sender, e);
}
}
}
#endregion
#region Sample entity
class Trade : INotifyPropertyChanged
{
protected string _Symbol;
protected int _Qty = 0;
protected DateTime _OrderPlaced = DateTime.Now;
public DateTime OrderPlaced
{
get { return _OrderPlaced; }
}
public string Symbol
{
get
{
return _Symbol;
}
set
{
_Symbol = value;
NotifyPropertyChanged("Symbol");
}
}
public int Qty
{
get
{
return _Qty;
}
set
{
_Qty = value;
NotifyPropertyChanged("Qty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
Added to TruelyObservableCollection event "ItemPropertyChanged":
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObservableCollectionTest
{
class Program
{
static void Main(string args)
{
// ATTN: Please note it's a "TrulyObservableCollection" that's instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
// REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });
Trades.CollectionChanged += Trades_CollectionChanged;
Trades.ItemPropertyChanged += PropertyChangedHandler;
Trades.RemoveAt(2);
Trades[0].Qty = 999;
Console.WriteLine("Hit any key to exit");
Console.ReadLine();
return;
}
static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
return;
}
static void Trades_CollectionChanged(object sender, EventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
return;
}
}
#region TrulyObservableCollection
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event PropertyChangedEventHandler ItemPropertyChanged;
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(sender, e);
}
}
}
#endregion
#region Sample entity
class Trade : INotifyPropertyChanged
{
protected string _Symbol;
protected int _Qty = 0;
protected DateTime _OrderPlaced = DateTime.Now;
public DateTime OrderPlaced
{
get { return _OrderPlaced; }
}
public string Symbol
{
get
{
return _Symbol;
}
set
{
_Symbol = value;
NotifyPropertyChanged("Symbol");
}
}
public int Qty
{
get
{
return _Qty;
}
set
{
_Qty = value;
NotifyPropertyChanged("Qty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
answered Feb 17 '13 at 8:01
Swab.JatSwab.Jat
7831917
7831917
You could use PropertyChanged from ObservableCollection directly, since it implements INotifyPropertyChanged.
– Dieter Meemken
Jun 3 '16 at 14:23
add a comment |
You could use PropertyChanged from ObservableCollection directly, since it implements INotifyPropertyChanged.
– Dieter Meemken
Jun 3 '16 at 14:23
You could use PropertyChanged from ObservableCollection directly, since it implements INotifyPropertyChanged.
– Dieter Meemken
Jun 3 '16 at 14:23
You could use PropertyChanged from ObservableCollection directly, since it implements INotifyPropertyChanged.
– Dieter Meemken
Jun 3 '16 at 14:23
add a comment |
I used Jack Kenyons answer to implement my own OC, but I'd like to point out one change i had to make to make it work. Instead of:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
I used this:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
It seems that the "e.NewItems" produces null if action is .Remove.
I think it needs further changes as well what if e.Action == replace
– jk.
Jul 29 '11 at 15:53
add a comment |
I used Jack Kenyons answer to implement my own OC, but I'd like to point out one change i had to make to make it work. Instead of:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
I used this:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
It seems that the "e.NewItems" produces null if action is .Remove.
I think it needs further changes as well what if e.Action == replace
– jk.
Jul 29 '11 at 15:53
add a comment |
I used Jack Kenyons answer to implement my own OC, but I'd like to point out one change i had to make to make it work. Instead of:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
I used this:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
It seems that the "e.NewItems" produces null if action is .Remove.
I used Jack Kenyons answer to implement my own OC, but I'd like to point out one change i had to make to make it work. Instead of:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
I used this:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
It seems that the "e.NewItems" produces null if action is .Remove.
answered Jan 12 '11 at 23:35
triazotantriazotan
1,82712232
1,82712232
I think it needs further changes as well what if e.Action == replace
– jk.
Jul 29 '11 at 15:53
add a comment |
I think it needs further changes as well what if e.Action == replace
– jk.
Jul 29 '11 at 15:53
I think it needs further changes as well what if e.Action == replace
– jk.
Jul 29 '11 at 15:53
I think it needs further changes as well what if e.Action == replace
– jk.
Jul 29 '11 at 15:53
add a comment |
Just adding my 2 cents on this topic. Felt the TrulyObservableCollection required the two other constructors as found with ObservableCollection:
public TrulyObservableCollection()
: base()
{
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in collection)
item.PropertyChanged += ItemPropertyChanged;
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);
HookupCollectionChangedEvent();
}
private void HookupCollectionChangedEvent()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
}
add a comment |
Just adding my 2 cents on this topic. Felt the TrulyObservableCollection required the two other constructors as found with ObservableCollection:
public TrulyObservableCollection()
: base()
{
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in collection)
item.PropertyChanged += ItemPropertyChanged;
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);
HookupCollectionChangedEvent();
}
private void HookupCollectionChangedEvent()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
}
add a comment |
Just adding my 2 cents on this topic. Felt the TrulyObservableCollection required the two other constructors as found with ObservableCollection:
public TrulyObservableCollection()
: base()
{
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in collection)
item.PropertyChanged += ItemPropertyChanged;
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);
HookupCollectionChangedEvent();
}
private void HookupCollectionChangedEvent()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
}
Just adding my 2 cents on this topic. Felt the TrulyObservableCollection required the two other constructors as found with ObservableCollection:
public TrulyObservableCollection()
: base()
{
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in collection)
item.PropertyChanged += ItemPropertyChanged;
HookupCollectionChangedEvent();
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);
HookupCollectionChangedEvent();
}
private void HookupCollectionChangedEvent()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
}
answered Aug 2 '13 at 9:36
pjdupreezpjdupreez
4841614
4841614
add a comment |
add a comment |
I know that I'm too late for this party, but maybe - it will help to someone..
Here you can find my implementation of ObservableCollectionEx. It has some features:
- it supports everything from ObservableCollection
- it's thread safe
- it supports ItemPropertyChanged event (it raises each time when Item.PropertyChanged item is fired)
- it supports filters (so, you could create ObservableCollectionEx, pass another collection as Source to it, and Filter with simple predicate. Very useful in WPF, I use this feature a lot in my applications). Even more - filter tracks changes of items via INotifyPropertyChanged interface.
Of course, any comments are appreciated ;)
1
Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :)
– Alexander
Jul 7 '13 at 6:23
@Alexander you're very wellcome :)
– chopikadze
Jul 7 '13 at 12:02
@chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks
– Shax
Jan 11 '17 at 8:06
The link is dead.
– user2655904
Jul 31 '17 at 5:39
add a comment |
I know that I'm too late for this party, but maybe - it will help to someone..
Here you can find my implementation of ObservableCollectionEx. It has some features:
- it supports everything from ObservableCollection
- it's thread safe
- it supports ItemPropertyChanged event (it raises each time when Item.PropertyChanged item is fired)
- it supports filters (so, you could create ObservableCollectionEx, pass another collection as Source to it, and Filter with simple predicate. Very useful in WPF, I use this feature a lot in my applications). Even more - filter tracks changes of items via INotifyPropertyChanged interface.
Of course, any comments are appreciated ;)
1
Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :)
– Alexander
Jul 7 '13 at 6:23
@Alexander you're very wellcome :)
– chopikadze
Jul 7 '13 at 12:02
@chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks
– Shax
Jan 11 '17 at 8:06
The link is dead.
– user2655904
Jul 31 '17 at 5:39
add a comment |
I know that I'm too late for this party, but maybe - it will help to someone..
Here you can find my implementation of ObservableCollectionEx. It has some features:
- it supports everything from ObservableCollection
- it's thread safe
- it supports ItemPropertyChanged event (it raises each time when Item.PropertyChanged item is fired)
- it supports filters (so, you could create ObservableCollectionEx, pass another collection as Source to it, and Filter with simple predicate. Very useful in WPF, I use this feature a lot in my applications). Even more - filter tracks changes of items via INotifyPropertyChanged interface.
Of course, any comments are appreciated ;)
I know that I'm too late for this party, but maybe - it will help to someone..
Here you can find my implementation of ObservableCollectionEx. It has some features:
- it supports everything from ObservableCollection
- it's thread safe
- it supports ItemPropertyChanged event (it raises each time when Item.PropertyChanged item is fired)
- it supports filters (so, you could create ObservableCollectionEx, pass another collection as Source to it, and Filter with simple predicate. Very useful in WPF, I use this feature a lot in my applications). Even more - filter tracks changes of items via INotifyPropertyChanged interface.
Of course, any comments are appreciated ;)
answered Apr 27 '13 at 10:20
chopikadzechopikadze
3,5311827
3,5311827
1
Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :)
– Alexander
Jul 7 '13 at 6:23
@Alexander you're very wellcome :)
– chopikadze
Jul 7 '13 at 12:02
@chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks
– Shax
Jan 11 '17 at 8:06
The link is dead.
– user2655904
Jul 31 '17 at 5:39
add a comment |
1
Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :)
– Alexander
Jul 7 '13 at 6:23
@Alexander you're very wellcome :)
– chopikadze
Jul 7 '13 at 12:02
@chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks
– Shax
Jan 11 '17 at 8:06
The link is dead.
– user2655904
Jul 31 '17 at 5:39
1
1
Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :)
– Alexander
Jul 7 '13 at 6:23
Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :)
– Alexander
Jul 7 '13 at 6:23
@Alexander you're very wellcome :)
– chopikadze
Jul 7 '13 at 12:02
@Alexander you're very wellcome :)
– chopikadze
Jul 7 '13 at 12:02
@chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks
– Shax
Jan 11 '17 at 8:06
@chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks
– Shax
Jan 11 '17 at 8:06
The link is dead.
– user2655904
Jul 31 '17 at 5:39
The link is dead.
– user2655904
Jul 31 '17 at 5:39
add a comment |
Simple solution for standard observablecollection that I've used:
DO NOT ADD to your property OR CHANGE it's inner items DIRECTLY, instead, create some temp collection like this
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
and add items or make changes to tmpList,
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
then pass it to your actual property by assignment.
ContentList=tmpList;
this will change whole property which causes notice the INotifyPropertyChanged as you need.
add a comment |
Simple solution for standard observablecollection that I've used:
DO NOT ADD to your property OR CHANGE it's inner items DIRECTLY, instead, create some temp collection like this
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
and add items or make changes to tmpList,
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
then pass it to your actual property by assignment.
ContentList=tmpList;
this will change whole property which causes notice the INotifyPropertyChanged as you need.
add a comment |
Simple solution for standard observablecollection that I've used:
DO NOT ADD to your property OR CHANGE it's inner items DIRECTLY, instead, create some temp collection like this
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
and add items or make changes to tmpList,
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
then pass it to your actual property by assignment.
ContentList=tmpList;
this will change whole property which causes notice the INotifyPropertyChanged as you need.
Simple solution for standard observablecollection that I've used:
DO NOT ADD to your property OR CHANGE it's inner items DIRECTLY, instead, create some temp collection like this
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
and add items or make changes to tmpList,
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
then pass it to your actual property by assignment.
ContentList=tmpList;
this will change whole property which causes notice the INotifyPropertyChanged as you need.
edited Mar 15 '14 at 10:24
answered Mar 15 '14 at 10:17


thinksomidthinksomid
193
193
add a comment |
add a comment |
I try this solution, but only works for me like a RaisePropertyChange("SourceGroupeGridView") when collection changed, that fired for each item add or changed.
The problem is in:
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Reset this action make a complete rebind of all items in groupedgrid, is equivalent at RaisePropertyChanged. When you use it all groups of gridview refreshed.
IF you, only want to refresh in UI the group of the new item, you don't use Reset action, you will need simulate a Add action in itemproperty with something like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = this.IndexOf((T)sender);
this.RemoveAt(index);
this.Insert(index, (T)sender);
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
OnCollectionChanged(a);
}
Sorry by my english, and thanks for the base code :),
I hope this helps someone ^_^
Enjoi!!
add a comment |
I try this solution, but only works for me like a RaisePropertyChange("SourceGroupeGridView") when collection changed, that fired for each item add or changed.
The problem is in:
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Reset this action make a complete rebind of all items in groupedgrid, is equivalent at RaisePropertyChanged. When you use it all groups of gridview refreshed.
IF you, only want to refresh in UI the group of the new item, you don't use Reset action, you will need simulate a Add action in itemproperty with something like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = this.IndexOf((T)sender);
this.RemoveAt(index);
this.Insert(index, (T)sender);
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
OnCollectionChanged(a);
}
Sorry by my english, and thanks for the base code :),
I hope this helps someone ^_^
Enjoi!!
add a comment |
I try this solution, but only works for me like a RaisePropertyChange("SourceGroupeGridView") when collection changed, that fired for each item add or changed.
The problem is in:
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Reset this action make a complete rebind of all items in groupedgrid, is equivalent at RaisePropertyChanged. When you use it all groups of gridview refreshed.
IF you, only want to refresh in UI the group of the new item, you don't use Reset action, you will need simulate a Add action in itemproperty with something like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = this.IndexOf((T)sender);
this.RemoveAt(index);
this.Insert(index, (T)sender);
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
OnCollectionChanged(a);
}
Sorry by my english, and thanks for the base code :),
I hope this helps someone ^_^
Enjoi!!
I try this solution, but only works for me like a RaisePropertyChange("SourceGroupeGridView") when collection changed, that fired for each item add or changed.
The problem is in:
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Reset this action make a complete rebind of all items in groupedgrid, is equivalent at RaisePropertyChanged. When you use it all groups of gridview refreshed.
IF you, only want to refresh in UI the group of the new item, you don't use Reset action, you will need simulate a Add action in itemproperty with something like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = this.IndexOf((T)sender);
this.RemoveAt(index);
this.Insert(index, (T)sender);
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
OnCollectionChanged(a);
}
Sorry by my english, and thanks for the base code :),
I hope this helps someone ^_^
Enjoi!!
edited May 31 '17 at 8:59
slavoo
3,978122936
3,978122936
answered Sep 18 '14 at 11:06
alberto sainzalberto sainz
111
111
add a comment |
add a comment |
Here's an extension method for the above solution...
public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
where T : INotifyPropertyChanged
{
var newList = new TrulyObservableCollection<T>();
if (list != null)
{
list.ForEach(o => newList.Add(o));
}
return newList;
}
You may want to explain the answer
– geedubb
Oct 23 '18 at 15:43
1
Here's a link that describes extension methods. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
– LawMan
Oct 23 '18 at 19:18
add a comment |
Here's an extension method for the above solution...
public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
where T : INotifyPropertyChanged
{
var newList = new TrulyObservableCollection<T>();
if (list != null)
{
list.ForEach(o => newList.Add(o));
}
return newList;
}
You may want to explain the answer
– geedubb
Oct 23 '18 at 15:43
1
Here's a link that describes extension methods. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
– LawMan
Oct 23 '18 at 19:18
add a comment |
Here's an extension method for the above solution...
public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
where T : INotifyPropertyChanged
{
var newList = new TrulyObservableCollection<T>();
if (list != null)
{
list.ForEach(o => newList.Add(o));
}
return newList;
}
Here's an extension method for the above solution...
public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
where T : INotifyPropertyChanged
{
var newList = new TrulyObservableCollection<T>();
if (list != null)
{
list.ForEach(o => newList.Add(o));
}
return newList;
}
edited May 31 '17 at 9:00
slavoo
3,978122936
3,978122936
answered Aug 7 '14 at 13:32


LawManLawMan
2,5002123
2,5002123
You may want to explain the answer
– geedubb
Oct 23 '18 at 15:43
1
Here's a link that describes extension methods. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
– LawMan
Oct 23 '18 at 19:18
add a comment |
You may want to explain the answer
– geedubb
Oct 23 '18 at 15:43
1
Here's a link that describes extension methods. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
– LawMan
Oct 23 '18 at 19:18
You may want to explain the answer
– geedubb
Oct 23 '18 at 15:43
You may want to explain the answer
– geedubb
Oct 23 '18 at 15:43
1
1
Here's a link that describes extension methods. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
– LawMan
Oct 23 '18 at 19:18
Here's a link that describes extension methods. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
– LawMan
Oct 23 '18 at 19:18
add a comment |
Instead of an ObservableCollection or TrulyObservableCollection, consider using a BindingList and calling the ResetBindings method.
For example:
private BindingList<TfsFile> _tfsFiles;
public BindingList<TfsFile> TfsFiles
{
get { return _tfsFiles; }
set
{
_tfsFiles = value;
NotifyPropertyChanged();
}
}
Given an event, such as a click your code would look like this:
foreach (var file in TfsFiles)
{
SelectedFile = file;
file.Name = "Different Text";
TfsFiles.ResetBindings();
}
My model looked like this:
namespace Models
{
public class TfsFile
{
public string ImagePath { get; set; }
public string FullPath { get; set; }
public string Name { get; set; }
public string Text { get; set; }
}
}
1
Good info on this method ofBindingList
, but there is a limitation to this approach that the other answers overcome: this technique relies on the value being changed in code and where a call toResetBindings()
can be added. Most of the other answers will work if the list's objects are altered through other means, such as unalterable code or from a binding to a second control.
– Bob Sammers
Mar 2 '17 at 18:44
add a comment |
Instead of an ObservableCollection or TrulyObservableCollection, consider using a BindingList and calling the ResetBindings method.
For example:
private BindingList<TfsFile> _tfsFiles;
public BindingList<TfsFile> TfsFiles
{
get { return _tfsFiles; }
set
{
_tfsFiles = value;
NotifyPropertyChanged();
}
}
Given an event, such as a click your code would look like this:
foreach (var file in TfsFiles)
{
SelectedFile = file;
file.Name = "Different Text";
TfsFiles.ResetBindings();
}
My model looked like this:
namespace Models
{
public class TfsFile
{
public string ImagePath { get; set; }
public string FullPath { get; set; }
public string Name { get; set; }
public string Text { get; set; }
}
}
1
Good info on this method ofBindingList
, but there is a limitation to this approach that the other answers overcome: this technique relies on the value being changed in code and where a call toResetBindings()
can be added. Most of the other answers will work if the list's objects are altered through other means, such as unalterable code or from a binding to a second control.
– Bob Sammers
Mar 2 '17 at 18:44
add a comment |
Instead of an ObservableCollection or TrulyObservableCollection, consider using a BindingList and calling the ResetBindings method.
For example:
private BindingList<TfsFile> _tfsFiles;
public BindingList<TfsFile> TfsFiles
{
get { return _tfsFiles; }
set
{
_tfsFiles = value;
NotifyPropertyChanged();
}
}
Given an event, such as a click your code would look like this:
foreach (var file in TfsFiles)
{
SelectedFile = file;
file.Name = "Different Text";
TfsFiles.ResetBindings();
}
My model looked like this:
namespace Models
{
public class TfsFile
{
public string ImagePath { get; set; }
public string FullPath { get; set; }
public string Name { get; set; }
public string Text { get; set; }
}
}
Instead of an ObservableCollection or TrulyObservableCollection, consider using a BindingList and calling the ResetBindings method.
For example:
private BindingList<TfsFile> _tfsFiles;
public BindingList<TfsFile> TfsFiles
{
get { return _tfsFiles; }
set
{
_tfsFiles = value;
NotifyPropertyChanged();
}
}
Given an event, such as a click your code would look like this:
foreach (var file in TfsFiles)
{
SelectedFile = file;
file.Name = "Different Text";
TfsFiles.ResetBindings();
}
My model looked like this:
namespace Models
{
public class TfsFile
{
public string ImagePath { get; set; }
public string FullPath { get; set; }
public string Name { get; set; }
public string Text { get; set; }
}
}
edited May 31 '17 at 9:01
slavoo
3,978122936
3,978122936
answered Jan 12 '17 at 23:33
ClubbieTimClubbieTim
10712
10712
1
Good info on this method ofBindingList
, but there is a limitation to this approach that the other answers overcome: this technique relies on the value being changed in code and where a call toResetBindings()
can be added. Most of the other answers will work if the list's objects are altered through other means, such as unalterable code or from a binding to a second control.
– Bob Sammers
Mar 2 '17 at 18:44
add a comment |
1
Good info on this method ofBindingList
, but there is a limitation to this approach that the other answers overcome: this technique relies on the value being changed in code and where a call toResetBindings()
can be added. Most of the other answers will work if the list's objects are altered through other means, such as unalterable code or from a binding to a second control.
– Bob Sammers
Mar 2 '17 at 18:44
1
1
Good info on this method of
BindingList
, but there is a limitation to this approach that the other answers overcome: this technique relies on the value being changed in code and where a call to ResetBindings()
can be added. Most of the other answers will work if the list's objects are altered through other means, such as unalterable code or from a binding to a second control.– Bob Sammers
Mar 2 '17 at 18:44
Good info on this method of
BindingList
, but there is a limitation to this approach that the other answers overcome: this technique relies on the value being changed in code and where a call to ResetBindings()
can be added. Most of the other answers will work if the list's objects are altered through other means, such as unalterable code or from a binding to a second control.– Bob Sammers
Mar 2 '17 at 18:44
add a comment |
If i know ObservableCollection make event only when we add/delete or move items in our collection. When we simly update some properties in collection items collection don`t signalize about it and UI will not be updated.
You can simly implement INotifyPropertyChange in your Model class.
And than when we update some propery in collection item it automatically will update UI.
public class Model:INotifyPropertyChange
{
//...
}
and than
public ObservableCollection<Model> {get; set;}
In my case i used ListView to Bind for this collection and in ItemTemplate set Binding to Model property and it work good.
Here is some snippet
Windows XAML :
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView
Margin="10"
BorderBrush="Black"
HorizontalAlignment="Center"
SelectedItem="{Binding SelectedPerson}"
ItemsSource="{Binding Persons}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
<Label Content="-"/>
<Label Content="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid
Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label
VerticalAlignment="Center"
Content="Name:"/>
<TextBox
Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Column="1"
Width="100"/>
<Label
VerticalAlignment="Center"
Grid.Row="1"
Content="Age:"/>
<TextBox
Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Row="1"
Grid.Column="1"
Width="100"/>
</Grid>
</Grid>
Model code example:
public class PersonModel:INotifyPropertyChanged
{
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public int Age
{
get => _age;
set
{
_age = value;
OnPropertyChanged();
}
}
private string _name;
private int _age;
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And ViewModel implementation:
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
Persons = new ObservableCollection<PersonModel>
{
new PersonModel
{
Name = "Jack",
Age = 30
},
new PersonModel
{
Name = "Jon",
Age = 23
},
new PersonModel
{
Name = "Max",
Age = 23
},
};
}
public ObservableCollection<PersonModel> Persons { get;}
public PersonModel SelectedPerson
{
get => _selectedPerson;
set
{
_selectedPerson = value;
OnPropertyChanged();
}
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private PersonModel _selectedPerson;
}
add a comment |
If i know ObservableCollection make event only when we add/delete or move items in our collection. When we simly update some properties in collection items collection don`t signalize about it and UI will not be updated.
You can simly implement INotifyPropertyChange in your Model class.
And than when we update some propery in collection item it automatically will update UI.
public class Model:INotifyPropertyChange
{
//...
}
and than
public ObservableCollection<Model> {get; set;}
In my case i used ListView to Bind for this collection and in ItemTemplate set Binding to Model property and it work good.
Here is some snippet
Windows XAML :
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView
Margin="10"
BorderBrush="Black"
HorizontalAlignment="Center"
SelectedItem="{Binding SelectedPerson}"
ItemsSource="{Binding Persons}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
<Label Content="-"/>
<Label Content="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid
Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label
VerticalAlignment="Center"
Content="Name:"/>
<TextBox
Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Column="1"
Width="100"/>
<Label
VerticalAlignment="Center"
Grid.Row="1"
Content="Age:"/>
<TextBox
Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Row="1"
Grid.Column="1"
Width="100"/>
</Grid>
</Grid>
Model code example:
public class PersonModel:INotifyPropertyChanged
{
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public int Age
{
get => _age;
set
{
_age = value;
OnPropertyChanged();
}
}
private string _name;
private int _age;
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And ViewModel implementation:
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
Persons = new ObservableCollection<PersonModel>
{
new PersonModel
{
Name = "Jack",
Age = 30
},
new PersonModel
{
Name = "Jon",
Age = 23
},
new PersonModel
{
Name = "Max",
Age = 23
},
};
}
public ObservableCollection<PersonModel> Persons { get;}
public PersonModel SelectedPerson
{
get => _selectedPerson;
set
{
_selectedPerson = value;
OnPropertyChanged();
}
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private PersonModel _selectedPerson;
}
add a comment |
If i know ObservableCollection make event only when we add/delete or move items in our collection. When we simly update some properties in collection items collection don`t signalize about it and UI will not be updated.
You can simly implement INotifyPropertyChange in your Model class.
And than when we update some propery in collection item it automatically will update UI.
public class Model:INotifyPropertyChange
{
//...
}
and than
public ObservableCollection<Model> {get; set;}
In my case i used ListView to Bind for this collection and in ItemTemplate set Binding to Model property and it work good.
Here is some snippet
Windows XAML :
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView
Margin="10"
BorderBrush="Black"
HorizontalAlignment="Center"
SelectedItem="{Binding SelectedPerson}"
ItemsSource="{Binding Persons}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
<Label Content="-"/>
<Label Content="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid
Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label
VerticalAlignment="Center"
Content="Name:"/>
<TextBox
Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Column="1"
Width="100"/>
<Label
VerticalAlignment="Center"
Grid.Row="1"
Content="Age:"/>
<TextBox
Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Row="1"
Grid.Column="1"
Width="100"/>
</Grid>
</Grid>
Model code example:
public class PersonModel:INotifyPropertyChanged
{
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public int Age
{
get => _age;
set
{
_age = value;
OnPropertyChanged();
}
}
private string _name;
private int _age;
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And ViewModel implementation:
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
Persons = new ObservableCollection<PersonModel>
{
new PersonModel
{
Name = "Jack",
Age = 30
},
new PersonModel
{
Name = "Jon",
Age = 23
},
new PersonModel
{
Name = "Max",
Age = 23
},
};
}
public ObservableCollection<PersonModel> Persons { get;}
public PersonModel SelectedPerson
{
get => _selectedPerson;
set
{
_selectedPerson = value;
OnPropertyChanged();
}
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private PersonModel _selectedPerson;
}
If i know ObservableCollection make event only when we add/delete or move items in our collection. When we simly update some properties in collection items collection don`t signalize about it and UI will not be updated.
You can simly implement INotifyPropertyChange in your Model class.
And than when we update some propery in collection item it automatically will update UI.
public class Model:INotifyPropertyChange
{
//...
}
and than
public ObservableCollection<Model> {get; set;}
In my case i used ListView to Bind for this collection and in ItemTemplate set Binding to Model property and it work good.
Here is some snippet
Windows XAML :
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView
Margin="10"
BorderBrush="Black"
HorizontalAlignment="Center"
SelectedItem="{Binding SelectedPerson}"
ItemsSource="{Binding Persons}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
<Label Content="-"/>
<Label Content="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid
Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label
VerticalAlignment="Center"
Content="Name:"/>
<TextBox
Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Column="1"
Width="100"/>
<Label
VerticalAlignment="Center"
Grid.Row="1"
Content="Age:"/>
<TextBox
Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Grid.Row="1"
Grid.Column="1"
Width="100"/>
</Grid>
</Grid>
Model code example:
public class PersonModel:INotifyPropertyChanged
{
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public int Age
{
get => _age;
set
{
_age = value;
OnPropertyChanged();
}
}
private string _name;
private int _age;
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And ViewModel implementation:
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
Persons = new ObservableCollection<PersonModel>
{
new PersonModel
{
Name = "Jack",
Age = 30
},
new PersonModel
{
Name = "Jon",
Age = 23
},
new PersonModel
{
Name = "Max",
Age = 23
},
};
}
public ObservableCollection<PersonModel> Persons { get;}
public PersonModel SelectedPerson
{
get => _selectedPerson;
set
{
_selectedPerson = value;
OnPropertyChanged();
}
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private PersonModel _selectedPerson;
}
edited Sep 4 '17 at 16:30
answered Sep 4 '17 at 13:24
Sviatoslav KindratSviatoslav Kindrat
112
112
add a comment |
add a comment |
Here is my version of the implementation. It checks and throws an error, if the objects in list doesnt implement INotifyPropertyChanged, so can't forget that issue while developing. On the outside you use the ListItemChanged Event do determine whether the list or the list item itself has changed.
public class SpecialObservableCollection<T> : ObservableCollection<T>
{
public SpecialObservableCollection()
{
this.CollectionChanged += OnCollectionChanged;
}
void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
AddOrRemoveListToPropertyChanged(e.NewItems,true);
AddOrRemoveListToPropertyChanged(e.OldItems,false);
}
private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
{
if (list == null) { return; }
foreach (object item in list)
{
INotifyPropertyChanged o = item as INotifyPropertyChanged;
if (o != null)
{
if (add) { o.PropertyChanged += ListItemPropertyChanged; }
if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
}
else
{
throw new Exception("INotifyPropertyChanged is required");
}
}
}
void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnListItemChanged(this, e);
}
public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);
public event ListItemChangedEventHandler ListItemChanged;
private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
{
if (ListItemChanged != null) { this.ListItemChanged(this, e); }
}
}
add a comment |
Here is my version of the implementation. It checks and throws an error, if the objects in list doesnt implement INotifyPropertyChanged, so can't forget that issue while developing. On the outside you use the ListItemChanged Event do determine whether the list or the list item itself has changed.
public class SpecialObservableCollection<T> : ObservableCollection<T>
{
public SpecialObservableCollection()
{
this.CollectionChanged += OnCollectionChanged;
}
void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
AddOrRemoveListToPropertyChanged(e.NewItems,true);
AddOrRemoveListToPropertyChanged(e.OldItems,false);
}
private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
{
if (list == null) { return; }
foreach (object item in list)
{
INotifyPropertyChanged o = item as INotifyPropertyChanged;
if (o != null)
{
if (add) { o.PropertyChanged += ListItemPropertyChanged; }
if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
}
else
{
throw new Exception("INotifyPropertyChanged is required");
}
}
}
void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnListItemChanged(this, e);
}
public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);
public event ListItemChangedEventHandler ListItemChanged;
private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
{
if (ListItemChanged != null) { this.ListItemChanged(this, e); }
}
}
add a comment |
Here is my version of the implementation. It checks and throws an error, if the objects in list doesnt implement INotifyPropertyChanged, so can't forget that issue while developing. On the outside you use the ListItemChanged Event do determine whether the list or the list item itself has changed.
public class SpecialObservableCollection<T> : ObservableCollection<T>
{
public SpecialObservableCollection()
{
this.CollectionChanged += OnCollectionChanged;
}
void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
AddOrRemoveListToPropertyChanged(e.NewItems,true);
AddOrRemoveListToPropertyChanged(e.OldItems,false);
}
private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
{
if (list == null) { return; }
foreach (object item in list)
{
INotifyPropertyChanged o = item as INotifyPropertyChanged;
if (o != null)
{
if (add) { o.PropertyChanged += ListItemPropertyChanged; }
if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
}
else
{
throw new Exception("INotifyPropertyChanged is required");
}
}
}
void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnListItemChanged(this, e);
}
public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);
public event ListItemChangedEventHandler ListItemChanged;
private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
{
if (ListItemChanged != null) { this.ListItemChanged(this, e); }
}
}
Here is my version of the implementation. It checks and throws an error, if the objects in list doesnt implement INotifyPropertyChanged, so can't forget that issue while developing. On the outside you use the ListItemChanged Event do determine whether the list or the list item itself has changed.
public class SpecialObservableCollection<T> : ObservableCollection<T>
{
public SpecialObservableCollection()
{
this.CollectionChanged += OnCollectionChanged;
}
void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
AddOrRemoveListToPropertyChanged(e.NewItems,true);
AddOrRemoveListToPropertyChanged(e.OldItems,false);
}
private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
{
if (list == null) { return; }
foreach (object item in list)
{
INotifyPropertyChanged o = item as INotifyPropertyChanged;
if (o != null)
{
if (add) { o.PropertyChanged += ListItemPropertyChanged; }
if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
}
else
{
throw new Exception("INotifyPropertyChanged is required");
}
}
}
void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnListItemChanged(this, e);
}
public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);
public event ListItemChangedEventHandler ListItemChanged;
private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
{
if (ListItemChanged != null) { this.ListItemChanged(this, e); }
}
}
edited Apr 16 '15 at 2:31
answered Apr 16 '15 at 2:25
MichaelMichael
546624
546624
add a comment |
add a comment |
Simple solution in 2 lines of code. Just use the copy constructor.
No need to write TrulyObservableCollection etc.
Example:
speakers.list[0].Status = "offline";
speakers.list[0] = new Speaker(speakers.list[0]);
Another method without copy constructor. You can use serialization.
speakers.list[0].Status = "offline";
//speakers.list[0] = new Speaker(speakers.list[0]);
var tmp = JsonConvert.SerializeObject(speakers.list[0]);
var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
speakers.list[0] = tmp2;
add a comment |
Simple solution in 2 lines of code. Just use the copy constructor.
No need to write TrulyObservableCollection etc.
Example:
speakers.list[0].Status = "offline";
speakers.list[0] = new Speaker(speakers.list[0]);
Another method without copy constructor. You can use serialization.
speakers.list[0].Status = "offline";
//speakers.list[0] = new Speaker(speakers.list[0]);
var tmp = JsonConvert.SerializeObject(speakers.list[0]);
var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
speakers.list[0] = tmp2;
add a comment |
Simple solution in 2 lines of code. Just use the copy constructor.
No need to write TrulyObservableCollection etc.
Example:
speakers.list[0].Status = "offline";
speakers.list[0] = new Speaker(speakers.list[0]);
Another method without copy constructor. You can use serialization.
speakers.list[0].Status = "offline";
//speakers.list[0] = new Speaker(speakers.list[0]);
var tmp = JsonConvert.SerializeObject(speakers.list[0]);
var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
speakers.list[0] = tmp2;
Simple solution in 2 lines of code. Just use the copy constructor.
No need to write TrulyObservableCollection etc.
Example:
speakers.list[0].Status = "offline";
speakers.list[0] = new Speaker(speakers.list[0]);
Another method without copy constructor. You can use serialization.
speakers.list[0].Status = "offline";
//speakers.list[0] = new Speaker(speakers.list[0]);
var tmp = JsonConvert.SerializeObject(speakers.list[0]);
var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
speakers.list[0] = tmp2;
edited Apr 1 '18 at 17:21
answered Apr 1 '18 at 16:54
singapore saravanansingapore saravanan
288
288
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f1427471%2fobservablecollection-not-noticing-when-item-in-it-changes-even-with-inotifyprop%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown