200

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..

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Joseph jun. Melettukunnel
  • 6,267
  • 20
  • 69
  • 90
  • Does this answer your question? [A better way of forcing data bound WPF ListBox to update?](https://stackoverflow.com/questions/253036/a-better-way-of-forcing-data-bound-wpf-listbox-to-update) – Berger Nov 26 '19 at 04:36

21 Answers21

195

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);
    }
}
Mike de Klerk
  • 11,906
  • 8
  • 54
  • 76
simon
  • 3,380
  • 2
  • 22
  • 23
  • 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 08:41
  • Updated the answer to use Replace instead of Reset as suggested by Chris – Alexander Pacha Mar 11 '15 at 08:42
  • 4
    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 06:57
  • Is there a reason why this class is sealed? – sluki Jul 01 '15 at 07:11
  • Interestingly enough to use this in WPF `ComboBox` I've had to fire both replace (for currently selected item to change correctly) and reset (for combobox dropdown menu to update). Obviously it looks like a failure from `ComboBox` implementation side though. – Predelnik Jul 25 '15 at 08:17
  • Thank you!!! I've banged my head on this for about 5 nights now before coming across this post. I can't believe it's now 2015 and something like TrulyObservableCollection is built into the .NET Framework. – Ian Lee Aug 31 '15 at 18:14
  • I believe that the [System.ComponentModel.BindingList](https://msdn.microsoft.com/en-us/library/ms132679(v=vs.110).aspx) does the same thing. – Stack Dec 24 '15 at 13:21
  • @Stack sort of but doesnt scale well http://stackoverflow.com/questions/4284663/difference-between-observablecollection-and-bindinglist – Shashank Shekhar Jan 15 '16 at 18:56
  • 8
    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);` – hyankov Dec 30 '16 at 17:06
  • 1
    I had problems using this solution when binding a TrulyObservableCollection property of some items which are also part of a TrulyObservableCollection. The problem was that the CollectionChanged even should not be used to recognize item changes. Instead of using the CollectionChanged event i created an ItemChanged event with their own EventArgs (containing the actual item) which is also useful if you need to know which item has been changed. – michip96 Apr 24 '17 at 10:24
  • 4
    Has anyone got an example of the usage of this class? – Dev Mar 27 '18 at 10:21
  • 2
    Provide a usage example please. – ThEpRoGrAmMiNgNoOb Nov 07 '19 at 04:08
137

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...)

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Martin Harris
  • 28,277
  • 7
  • 90
  • 101
  • Sorry Harris, but what Event do I have to fire in EntityViewModel so that ContentCollectionChanged gets called? – Joseph jun. Melettukunnel Sep 15 '09 at 14:54
  • 44
    note that if you don't want to implement the event management yourself, you can use a BindingList in place of ObservableCollection. It will then automatically forward EntityViewModel.PropertyChanged events as ListChanged events where ListChangedType == ItemChanged. – mjeanes Sep 15 '09 at 15:35
  • Where is BindingList in the BCL? – Mike Cole May 19 '11 at 20:52
  • Ah, DOH, Silverlight doesn't have BindingList. – Mike Cole May 19 '11 at 20:53
  • 15
    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 06 '11 at 11:45
  • It does fire when an item itself is changed. This doesn't mean it fires when an property on an item is changed – Torben Junker Kjær Oct 05 '11 at 13:25
  • 11
    What happen if I invoke `_contentList.Clear()`? No one will unsubscribe from `PropertyChanged`! – Paolo Moretti Aug 03 '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
  • By change the msdn is referring to the location with in the collection not the items state, which as they have moved as well does cause confusion – MikeT Oct 09 '14 at 17:04
  • the reason for this is MS are expecting you to use a collection control to view the collection and then have the contained control monitor the item its bound to, in this situation all the collection container needs to do is remove or spawn contained controls as the collection changes, of course a major pain if your wanting to do anything other than bind a control to the item – MikeT Oct 09 '14 at 17:09
  • @Martin Harris: you don't need the if e.Action as all actions are a combination of add and remove, as your code currently stands it can't monitor replace or reset events, move either though less important as the handler stay attached – MikeT Oct 09 '14 at 17:15
  • @Martin Harris: thx your for answer. I have implemented your solution. Now I have got the EntityViewModelPropertyChanged event handler called every time a property of the Item in the ObservableCollection is changed. However, my view (ItemsControl) is still not updated. Am I missing anything? I suppose the EntityViewModelPropertyChanged should not be empty? – Frank Liu Feb 07 '15 at 12:02
  • Becareful with this approach. There will be memory leaks if you do not handle Reset action and remove Propertychanged handler – WPFKK Sep 12 '19 at 20:08
  • Suppose list has 1000 items. I think that main issue weak event handling. XAML, OOP considerations killer for UI... I do not propose if you use two way binding for large list. – Nuri YILMAZ Aug 13 '21 at 10:21
40

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 existing CollectionChanged:
  • To aid backward compatibility.
  • So more relevant detail can be given: ItemPropertyChangedEventArgs derives from PropertyChangedEventArgs, adding CollectionIndex to the existing data.
  • 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 the CollectionChanged 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)");
        }
    }
}
Bob Sammers
  • 3,080
  • 27
  • 33
  • 1
    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 02 '17 at 18:13
  • Thank you for your great work! May I suggest you publish this in a gist on GitHub or somewhere else? – simon Sep 08 '20 at 14:23
  • @simon Thank you. It's published here - why would publishing as a gist help? (Genuine question!) Downside is, it would double the number of places to maintain the code... – Bob Sammers Sep 09 '20 at 11:26
  • @BobSammers Just noticed that one can follow updates here too :) and this really makes gist unnecessary... – simon Sep 09 '20 at 14:44
  • One thing that I'm surprised to not see in more solutions is mention of DispatcherQueue. I found that regardless of solution, my application would crash because the properties in question are being modified on a worker thread and not a UI thread. I solved this by assigning the correct DispatcherQueue to the ObservableCollection subclass, and using it to dispatch the work from the OnCollectionChanged() method. However, the absence of discussion of this problem on this answer makes me wonder if I'm doing something terribly wrong in the first place. Any input anyone? @BobSammers? Thanks! – biomiker Aug 26 '22 at 22:05
  • That's a point worth making, @biomiker. It might be nice to have a more thread-safe version of this class, but the short answer to your question is, "because the underlying `ObservableCollection` isn't thread-safe." The advantages of having a multi-threading-aware class have to be weighed against the overheads and added complexity: in many cases the object is only accessed from one thread. `System.Collections.Concurrent` might offer a suitable base class. Note that the .net designers have chosen to add this new namespace, rather than simply add `Dispatcher` processing to existing collections. – Bob Sammers Sep 23 '22 at 14:15
21

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' - I don't know, why
            NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            OnCollectionChanged(args);
        }
    }
}
peterh
  • 11,875
  • 18
  • 85
  • 108
Jack Kenyon
  • 211
  • 2
  • 2
18

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.

mjeanes
  • 1,606
  • 10
  • 6
8

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;
}
  • Best solution in my opinion, works great. You do have a typo in your example at the top-- "public ObservableCollection {get; set;}" is missing a variable name. In my scenario, I added a DTO model/class that implemented INotifyPropertyChanged, which would then be the type used for the ObservableCollection. Was a little more work, but definitely the most natural solution here. – Brian Mar 29 '22 at 05:57
7

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
}
Swab.Jat
  • 1,210
  • 1
  • 14
  • 19
6

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);
    }
pjdupreez
  • 659
  • 1
  • 8
  • 19
  • This wont work for replacements. And possible memory leaks when items from collection are removed – nawfal Aug 07 '20 at 08:38
6

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.

triazotan
  • 2,192
  • 1
  • 22
  • 32
4

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

chopikadze
  • 4,219
  • 26
  • 30
  • 1
    Большое спасибо! Many thanks for sharing that! You saved me numerous hours by not having to write my own implementation! :) – Alexander Jul 07 '13 at 06:23
  • @chopikadze, i am unable to download the cs file of your ObservableCollectionEx can you kindly fix it. Thanks – Shax Jan 11 '17 at 08:06
  • 1
    The link is dead. –  Jul 31 '17 at 05:39
  • The explanation of the approach is available in web-archive (a kind of article) - [earlier](https://web.archive.org/web/20130316185250/http://george.softumus.com/2013/01/extended-observablecollection.html) and [later](https://web.archive.org/web/20160427015007/http://george.softumus.com/extended-observablecollection/), but unfortunately the code wasn't archived at all :( – AntonK Mar 24 '23 at 12:38
2

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.

thinksomid
  • 29
  • 3
  • and what if the collection is bound to some element (like `DataGrid`, `ListBox` or `ItemsContainer`), which allows the user to modfiy elements' properties through UI? Your manual approach will require a lot more code to handle such widely-used case. – AntonK Mar 24 '23 at 12:42
2

To Trigger OnChange in ObservableCollection List

  1. Get index of selected Item
  2. Remove the item from the Parent
  3. Add the item at same index in parent

Example:

int index = NotificationDetails.IndexOf(notificationDetails);
NotificationDetails.Remove(notificationDetails);
NotificationDetails.Insert(index, notificationDetails);
1

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;
}  
slavoo
  • 5,798
  • 64
  • 37
  • 39
LawMan
  • 3,469
  • 1
  • 29
  • 32
  • You may want to explain the answer – geedubb Oct 23 '18 at 15:43
  • 1
    Here's a link that describes extension methods. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods – LawMan Oct 23 '18 at 19:18
1

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!!

slavoo
  • 5,798
  • 64
  • 37
  • 39
1

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


}
Michael
  • 938
  • 1
  • 10
  • 34
  • 1
    To enforce it compile time you could have generic constraint of `INotifyPropertyChanged` on `T` – nawfal Aug 07 '20 at 08:41
  • This class has been highly useful. One thing I added to it was a Find implementation almost exactly like what is on Lists. I found that when searching in memory collections I could get a performance boost using Find over FirstOrDefault since it searches the collection via the index and not the IEnumerable. – b.pell Aug 26 '20 at 19:46
1

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

    }
}
slavoo
  • 5,798
  • 64
  • 37
  • 39
Lysoll
  • 644
  • 7
  • 4
  • 3
    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 02 '17 at 18:44
1

I see most examples here placing INotifyPropertyChanged constraint on the generic type which forces the model to Implement INotifyPropertyChanged.

If you follow the examples that place INotifyPropertyChanged constraints on the model, it's as good as implementing INotifyPropertyChanged in your model and allow ObservableCollection to handle the Update property change.

But if you do not want your model to implement INotifyPropertyChanged, you can try this.

CustomObservableCollection

 public class CustomObservableCollection<T> : ObservableCollection<T>
 {

      public void Refresh(T item)
      {
          var index = IndexOf(item);

          RemoveAt(index);
          Insert(index, item);

          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, IndexOf(item)));
      }
 }

Model

public class Person
{
    public string FirstName { get; set; }   
    public string LastName { get; set; }   
    public int Age { get; set; }   
}

ViewModel

 public class PersonViewModel
 {       

     public PersonViewModel(){

          People=new CustomObservableCollection<Person>();

     }
    
     private void AddPerson(){

        People.Add(new Person(){
            FirstName="John",
            LastName="Doe",
            Age=20
        });

      }
    
      private void UpdatePerson(){

         var person=People.Where(...).FirstOrDefault();
         person.Age=25;

         People.Refresh(person);
      }

      public CustomObservableCollection<Person> People { get; set; } 
    
    }
Bloggrammer
  • 921
  • 8
  • 21
0

You can also use this extension method to easily register a handler for item property change in relevant collections. This method is automatically added to all the collections implementing INotifyCollectionChanged that hold items that implement INotifyPropertyChanged:

public static class ObservableCollectionEx
{
    public static void SetOnCollectionItemPropertyChanged<T>(this T _this, PropertyChangedEventHandler handler)
        where T : INotifyCollectionChanged, ICollection<INotifyPropertyChanged> 
    {
        _this.CollectionChanged += (sender,e)=> {
            if (e.NewItems != null)
            {
                foreach (Object item in e.NewItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged += handler;
                }
            }
            if (e.OldItems != null)
            {
                foreach (Object item in e.OldItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged -= handler;
                }
            }
        };
    }
}

How to use:

public class Test
{
    public static void MyExtensionTest()
    {
        ObservableCollection<INotifyPropertyChanged> c = new ObservableCollection<INotifyPropertyChanged>();
        c.SetOnCollectionItemPropertyChanged((item, e) =>
        {
             //whatever you want to do on item change
        });
    }
}
ed22
  • 1,127
  • 2
  • 14
  • 30
0

For me helps this trick - removeAt and insert to replace item, during this "Change" - events rise properly.

private ObservableCollection<CollectionItem> collection = new ObservableCollection<CollectionItem>();

public void Update(CollectionItem newItem, CollectionItem old ) {
            int j = collection.IndexOf(old);
            collection.RemoveAt(j); 
            collection.Insert(j, newComplexCondition);
        }
Mikhail Kh
  • 176
  • 1
  • 8
0

Having had the same problem, I thought I'd post my solution using a class derived from ObservableCollection. It doesn't add a great deal to the similar implementations above, but it does use the PropertyChangedEventManager, which has two advantages: 1. it uses weak events so the problems of not unhooking the events and any resulting memory leaks are removed. 2. it allows the specific properties that, when changed, will trigger the CollectionChangedEvent to be specified.

In addition, for those like @Martin Harris who are confused about the behaviour of ObservableCollection, may I recommend this excellent article.

/// <summary>
/// Implements an ObservableCollection that raises a CollectionChanged (Reset) event if an item in the collection raises PropertyChanged
/// The property name or names mey be optionally specified.
/// Note, could have performance issues if used on a large collection.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class ObservableCollectionResetOnItemChange<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    public IEnumerable<string> PropertyNames { get; private set; }

    public ObservableCollectionResetOnItemChange(IEnumerable<string> propertyNames = null)
    {
        PropertyNames = propertyNames?? new List<string>();
        CollectionChanged += OnCollectionChanged;
    }

    public ObservableCollectionResetOnItemChange(string propertyName = null) :
        this(propertyName is null ? null : new List<string>() { propertyName } )
    {
    }

    public ObservableCollectionResetOnItemChange(IEnumerable<T> items, IEnumerable<string> propertyNames = null) :
        this(propertyNames)
    {
        foreach (T item in items)
        {
            {
                Add(item);
            }
        }
    }

    public ObservableCollectionResetOnItemChange(IEnumerable<T> items, string propertyName = null) :
        this(items, propertyName is null ? null : new List<string>() { propertyName })
    {
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (T item in e.NewItems)
            {
                if (PropertyNames.Any())
                {
                    foreach (string name in PropertyNames)
                    {
                        PropertyChangedEventManager.AddHandler(item, ItemPropertyChanged, name);
                    }
                }
                else
                {
                    PropertyChangedEventManager.AddHandler(item, ItemPropertyChanged, string.Empty);
                }
            }
        }
    }

    private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        OnCollectionChanged(args);
    }
}
Dave
  • 3,429
  • 2
  • 26
  • 29
-1

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;