none
ObservableCollection<T> - Listen for changes in child elements?

    Question

  • I've seen several threads about this, but none that I could find which helped much.

    I have an ObservableCollection<T> which holds a number of different child elements. I get the expected notifications for adding, removing etc. What I really could use is a collection which additionally raises notifications when a child element (implenting INotifyPropertyChanged) has a property change.

    I'd prefer to not have the Collection have to listen to specific events, but rather listen for "any" event raised by a child.

    I was thinking that maybe I could specialize the ObservableCollection<T> class somehow, maybe by using a binding object or something that via reflection could look for properties to bind to as a child is added, and raise a generic event ("ChildElementPropertyChanged")  when one of the child properties raises an event.

    Does something like this already exist? Any thoughts about nice ways to implement this?

    Thanks,
    Hedley
    Wednesday, March 10, 2010 8:52 PM

Answers

  • Hi Hedley,

    The following is a complete sample to do what you want.

     public class MyObservableCollection<T>: ObservableCollection<T>
        {
            public class ChildElementPropertyChangedEventArgs : EventArgs
            {
                public object ChildElement { get; set; }
                public ChildElementPropertyChangedEventArgs(object item)
                {
                    ChildElement = item;
                }
            }
            public delegate void ChildElementPropertyChangedEventHandler(ChildElementPropertyChangedEventArgs e);
            public event ChildElementPropertyChangedEventHandler ChildElementPropertyChanged;
            private void OnChildElementPropertyChanged(object childelement)
            {
                if (ChildElementPropertyChanged != null)
                {
                    ChildElementPropertyChanged(new ChildElementPropertyChangedEventArgs(childelement));
                }
            }

            protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                base.OnCollectionChanged(e);
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                {
                    foreach (T item in e.NewItems)
                    {
                        INotifyPropertyChanged convertedItem = item as INotifyPropertyChanged;
                        convertedItem.PropertyChanged += new PropertyChangedEventHandler(convertedItem_PropertyChanged);
                   }              
                }
                else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
                {
                    foreach (T item in e.OldItems)
                    {
                        INotifyPropertyChanged convertedItem = item as INotifyPropertyChanged;
                        convertedItem.PropertyChanged -= new PropertyChangedEventHandler(convertedItem_PropertyChanged);
                    }
                }
            }

            void convertedItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                OnChildElementPropertyChanged(sender);
            }
        }

     private void Window_Loaded(object sender, RoutedEventArgs e)
      {            
                MyObservableCollection<Person> people = new MyObservableCollection<Person>();
                people.Add(new Person() { Name = "Kate", Age = 23 });
                people.Add(new Person() { Name = "John", Age = 24 });
                people.ChildElementPropertyChanged += new MyObservableCollection<Person>.ChildElementPropertyChangedEventHandler(people_ChildElementPropertyChanged);
                people[0].Name = "new name";
      }

      void people_ChildElementPropertyChanged(MyObservableCollection<Person>.ChildElementPropertyChangedEventArgs e)
      {
                Console.WriteLine((e.ChildElement as Person).Name);
      }

    Hope this helps.
    If you have any question, please feel free to let me know.

    Sincerely,
    Linda Liu

    MSDN Subscriber Support in Forum 
    If you have any feedback on our support, please contact msdnmg@microsoft.com

    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    • Marked as answer by TCLMKR Thursday, March 11, 2010 3:25 PM
    Thursday, March 11, 2010 10:07 AM
  • I'd add one more thing: an override for ClearItems to remove the attached handlers if Clear() is called on the collection. Something like this...

    protected override void ClearItems()
    {
        foreach (INotifyPropertyChanged item in Items)
        {
            item.PropertyChanged -= PropertyChangedEventHandler;
        }

        base.ClearItems();
    }
    • Edited by kjh7r Tuesday, March 16, 2010 4:08 PM corrected typo
    • Marked as answer by TCLMKR Tuesday, March 16, 2010 8:40 PM
    Tuesday, March 16, 2010 4:06 PM

All replies

  • ObservableCollection doesn't work that way already? I thought change events bubbled up.
    If my response answers your question, please mark it as the "Answer" by clicking that button above my post.

    My blog: http://www.RyanVice.net/
    Wednesday, March 10, 2010 9:07 PM
  • Hedley,

    I wrote something that does this, and it works for any INotifyCollectionChanged or IBindingList implementation holding INotifyPropertyChanged instances.

    I initially wrote this for my Master-detail aggregation sample, which is posted to the Expression Blend Gallery under Collection Aggregator for Master-Detail Windows.

    You should be able to just use (or adapt) that class for your purposes.

    -Reed


    Reed Copsey, Jr. - http://reedcopsey.com
    Wednesday, March 10, 2010 9:08 PM
  • The changes that bubble up are adds, removes etc as the list itself is concerned. Individual properties on the elements of the list aren't listened to by the parent.

    Thanks,
    Hedley
    Wednesday, March 10, 2010 9:17 PM
  • The changes that bubble up are adds, removes etc as the list itself is concerned. Individual properties on the elements of the list aren't listened to by the parent.

    Thanks,
    Hedley
    Nope.  If you want that to happen, you need to explicitly subscribe to them.  It's a bit of a chore, but mostly bookkeeping.

    See my previous link - I have a working example of this in ObservableCollectionAggregator.cs, in that download.


    Reed Copsey, Jr. - http://reedcopsey.com
    Wednesday, March 10, 2010 9:43 PM
  • Bummer...
    If my response answers your question, please mark it as the "Answer" by clicking that button above my post.

    My blog: http://www.RyanVice.net/
    Wednesday, March 10, 2010 9:48 PM
  • Reed,

    Your sample was very interesting, and elegantly implemented.

    I'm going to study it a bit more, to see if it's the right fit for my problem.

    The problem I'm running up against is, as always, time.

    I have a solution that "works" but is not ideal, and I'm searching for alternatives.

    Thanks,



    Hedley
    Wednesday, March 10, 2010 11:36 PM
  • Hi Hedley,

    The following is a complete sample to do what you want.

     public class MyObservableCollection<T>: ObservableCollection<T>
        {
            public class ChildElementPropertyChangedEventArgs : EventArgs
            {
                public object ChildElement { get; set; }
                public ChildElementPropertyChangedEventArgs(object item)
                {
                    ChildElement = item;
                }
            }
            public delegate void ChildElementPropertyChangedEventHandler(ChildElementPropertyChangedEventArgs e);
            public event ChildElementPropertyChangedEventHandler ChildElementPropertyChanged;
            private void OnChildElementPropertyChanged(object childelement)
            {
                if (ChildElementPropertyChanged != null)
                {
                    ChildElementPropertyChanged(new ChildElementPropertyChangedEventArgs(childelement));
                }
            }

            protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                base.OnCollectionChanged(e);
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                {
                    foreach (T item in e.NewItems)
                    {
                        INotifyPropertyChanged convertedItem = item as INotifyPropertyChanged;
                        convertedItem.PropertyChanged += new PropertyChangedEventHandler(convertedItem_PropertyChanged);
                   }              
                }
                else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
                {
                    foreach (T item in e.OldItems)
                    {
                        INotifyPropertyChanged convertedItem = item as INotifyPropertyChanged;
                        convertedItem.PropertyChanged -= new PropertyChangedEventHandler(convertedItem_PropertyChanged);
                    }
                }
            }

            void convertedItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                OnChildElementPropertyChanged(sender);
            }
        }

     private void Window_Loaded(object sender, RoutedEventArgs e)
      {            
                MyObservableCollection<Person> people = new MyObservableCollection<Person>();
                people.Add(new Person() { Name = "Kate", Age = 23 });
                people.Add(new Person() { Name = "John", Age = 24 });
                people.ChildElementPropertyChanged += new MyObservableCollection<Person>.ChildElementPropertyChangedEventHandler(people_ChildElementPropertyChanged);
                people[0].Name = "new name";
      }

      void people_ChildElementPropertyChanged(MyObservableCollection<Person>.ChildElementPropertyChangedEventArgs e)
      {
                Console.WriteLine((e.ChildElement as Person).Name);
      }

    Hope this helps.
    If you have any question, please feel free to let me know.

    Sincerely,
    Linda Liu

    MSDN Subscriber Support in Forum 
    If you have any feedback on our support, please contact msdnmg@microsoft.com

    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    • Marked as answer by TCLMKR Thursday, March 11, 2010 3:25 PM
    Thursday, March 11, 2010 10:07 AM
  • I think this will work. I didn't want to have to subscribe to the individual property changes, I just need to know if something changed.

    Thanks Linda!
    Hedley
    Thursday, March 11, 2010 3:26 PM
  • I'd add one more thing: an override for ClearItems to remove the attached handlers if Clear() is called on the collection. Something like this...

    protected override void ClearItems()
    {
        foreach (INotifyPropertyChanged item in Items)
        {
            item.PropertyChanged -= PropertyChangedEventHandler;
        }

        base.ClearItems();
    }
    • Edited by kjh7r Tuesday, March 16, 2010 4:08 PM corrected typo
    • Marked as answer by TCLMKR Tuesday, March 16, 2010 8:40 PM
    Tuesday, March 16, 2010 4:06 PM
  • Yep. Good point, thanks!
    Hedley
    Tuesday, March 16, 2010 8:40 PM
  • This example seems to work only for object implementing INotifyPropertyChanged; otherwise the following conversion yields to null:

     INotifyPropertyChanged convertedItem = item as INotifyPropertyChanged;

    convertedItem.PropertyChanged += new PropertyChangedEventHandler(convertedItem_PropertyChanged);  //exception here

    If this is intended, why don't bind T to be a type that implements  INotifyPropertyChanged as follows?

     public class MyObservableCollection<T>: ObservableCollection<T> where T : INotifyPropertyChanged{ ... }

    Is there a way to make this work for classes not implementing INotifyPropertyChanged too?

    Thank you.


        



    Monday, April 16, 2012 12:59 PM
  • Is there a way to make this work for classes not implementing INotifyPropertyChanged too?   

    You need some kind of notification of the property change.
    Implementing INotifyPropertyChanged is the most common way to do so.
    The are other APIs using different interfaces (e.g. BindingList
    fires an  ListChanged event with an ItemChanged flag)
    Yet another mechanism are depedency properties.
    If you want to listen to depedency property changes from outside the dependency object
    you may want to use a DependenyPropertyDescriptor.

    As for POCOs aka simple CLR objects there is afaics no
    (ordinary) way to get notified on a property change.

    Chris
    Monday, April 16, 2012 3:15 PM