Using Silverlight and MVVM you often use property change tracking mechanisms, such as implementing INotifyPropertyChanged and using ObservableCollections (which implement INotifyCollectionChanged). This is fine when you are only interested in letting the UI synchronise with your data in the ViewModel (and vice-versa) but sometimes you need to track these changes in the code, either within your own class or in another class elsewhere. I had this issue recently, where I needed to know in my "Results" ViewModel when anything changed in any of my various "Input" ViewModels so that I could invalidate the results (so the user knows they need to re-calculate). In order to achieve this I needed to listen to events in the following 3 categories on many properties:
  • Property changed (setter called)
  • Collection changed (items added/removed)
  • Collection item property changed (element of a list has a property changed)
Rather than having to write event handling code and logic everywhere, I decided to design a base class that my "Input" viewmodels can inherit from that would take care of rolling up these 3 scenarios into a single event. In my "Results" class I then can simply add a listener for the one "InputsChanged" event. My view model base derives from a base implementing INotifyPropertyChanged and looks as follows:

public abstract class ResultsInputViewModelBase : ViewModelBase
    {
        /// <summary>
        /// A list of tracked properties that implement INotifyCollectionChanged, therefore need the CollectionChanged tracking
        /// </summary>
        private Dictionary<string, PropertyInfo> _collectionChangingPropertiesToTrack = new Dictionary<string, PropertyInfo>();

        /// <summary>
        /// A list of tracked properties that implement IEnumerable, therefore may need the items tracking
        /// </summary>
        private Dictionary<string, PropertyInfo> _enumerablePropertiesToTrack = new Dictionary<string, PropertyInfo>();

        /// <summary>
        /// Initializes a new instance of the <see cref="ResultsInputViewModelBase" /> class. In the base constructor we setup the auto-tracking feature, which attaches to PropertyChangedEvent and prepares for tracking CollectionChanged and sub-items PropertyChanged events
        /// </summary>
        public ResultsInputViewModelBase()
        {
            // subscribe the property changed event for this instance
            this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.ResultsInputViewModelBase_PropertyChanged);

            // look for properties that implement INotifyCollectionChanged (observable collections) in the tracked properties so we can auto-track these aswel
            foreach (string propertyName in this.TrackedInputProperties)
            {
                object currentValue = this;

                // find the property info using reflection
                if (!string.IsNullOrWhiteSpace(propertyName))
                {
                    PropertyInfo pi = null;

                    // nested property?
                    if (propertyName.Contains("."))
                    {
                        // child property - iterate the tree
                        Type currentType = this.GetType();

                        string[] props = propertyName.Split('.');
                        int pc = 0;
                        foreach (string p in props)
                        {
                            pc++;
                            pi = currentType.GetProperty(p);
                            if (pi != null && pc < props.Length)
                            {
                                currentType = pi.PropertyType;
                                currentValue = pi.GetValue(currentValue, null);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                    else
                    {
                        // direct property
                        pi = this.GetType().GetProperty(propertyName);
                    }

                    if (pi != null)
                    {
                        // does this property implement INotifyCollectionChanged?
                        if (typeof(INotifyCollectionChanged).IsAssignableFrom(pi.PropertyType))
                        {
                            // add the PropertyInfo to the list of tracked collections, so we can attach when the value != null
                            this._collectionChangingPropertiesToTrack.Add(propertyName, pi);

                            // incase its already != null, we can attempt to attach now
                            this.TryAttachCollectionTracker(pi, currentValue);
                        }

                        // does this property implement IEnumerable?
                        if (typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))
                        {
                            // add the PropertyInfo to the list of tracked collections, so we can attach when the value != null
                            this._enumerablePropertiesToTrack.Add(propertyName, pi);

                            // incase its already != null, we can attempt to attach now
                            this.TryAttachEnumeratedPropertyChangeTracker(pi, currentValue);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Event is fired whenever a tracked property or collection is changed in the derived class
        /// </summary>
        public event EventHandler InputsChanged;

        /// <summary>
        /// Gets the properties that are tracked in this base class. Implement in a derived class to signal each property which needs to raise the InputsChanged event
        /// </summary>
        protected abstract string[] TrackedInputProperties
        {
            get;
        }

        /// <summary>
        /// Provides a way for derived classes to inform that something in their inputs that requires a re-calculation has changed,
        /// although they shouldn't need to call this manually - put the property name in the TrackedInputProperties instead
        /// </summary>
        protected void OnInputsChanged()
        {
            if (this.InputsChanged != null)
            {
                this.InputsChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Attempts to get the value of a property that implements INotifyCollectionChanged, if the value is non-null, attaches to the CollectionChanged event
        /// </summary>
        /// <param name="pi">Property Info instance of the target property</param>
        /// <param name="container">The object containing the collection property</param>
        private void TryAttachCollectionTracker(PropertyInfo pi, object container)
        {
            // read the value of the property for the current instance
            INotifyCollectionChanged collection_propValue = pi.GetValue(container, null) as INotifyCollectionChanged;

            // if the property has a value, we can attach to its CollectionChanged event
            if (collection_propValue != null)
            {
                collection_propValue.CollectionChanged += new NotifyCollectionChangedEventHandler(this.ResultsInputViewModelBase_CollectionChanged);
            }
        }

        /// <summary>
        /// Attempts to get the value of a property that implements IEnumerable, if the value is non-null, attaches to the child elements PropertyChanged event (if implemented)
        /// </summary>
        /// <param name="pi">Property Info instance of the target property</param>
        /// <param name="container">The object containing the enumerable property</param>
        private void TryAttachEnumeratedPropertyChangeTracker(PropertyInfo pi, object container)
        {
            // read the value of the property for the current instance
            IEnumerable collection_propValue = pi.GetValue(container, null) as IEnumerable;

            // if the property has a value, we can attach to its CollectionChanged event
            if (collection_propValue != null)
            {
                foreach (var element in collection_propValue)
                {
                    if (element is ResultsInputViewModelBase)
                    {
                        ((ResultsInputViewModelBase)element).InputsChanged += this.InputsChanged;
                    }
                    else if (element is INotifyPropertyChanged)
                    {
                        ((INotifyPropertyChanged)element).PropertyChanged += new PropertyChangedEventHandler(this.ResultsInputViewModelBase_TrackedElementPropertyChanged);
                    }
                }
            }
        }

        /// <summary>
        /// Fires when any property that raises PropertyChanged event is called in the derived class
        /// </summary>
        /// <param name="sender">Object raising the event</param>
        /// <param name="e">Event arguments</param>
        private void ResultsInputViewModelBase_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            // if the property that has raised the event is one of our tracked properties, we can apply further processing logic
            if (Array.IndexOf(this.TrackedInputProperties, e.PropertyName) >= 0)
            {
                object currentValue = this;

                // find the property info using reflection
                if (!string.IsNullOrWhiteSpace(e.PropertyName))
                {
                    PropertyInfo pi = null;

                    // nested property?
                    if (e.PropertyName.Contains("."))
                    {
                        // child property - iterate the tree
                        Type currentType = this.GetType();

                        string[] props = e.PropertyName.Split('.');
                        int pc = 0;
                        foreach (string p in props)
                        {
                            pc++;
                            pi = currentType.GetProperty(p);
                            if (pi != null && pc < props.Length)
                            {
                                currentType = pi.PropertyType;
                                currentValue = pi.GetValue(currentValue, null);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                }

                // if the value of a changeable collection has just been set, we need to (re-)attach our collection tracker
                if (this._collectionChangingPropertiesToTrack.ContainsKey(e.PropertyName))
                {
                    this.TryAttachCollectionTracker(this._collectionChangingPropertiesToTrack[e.PropertyName], currentValue);
                }

                // if the value of a enumerable has just been set, we need to (re-)attach our collection tracker
                if (this._enumerablePropertiesToTrack.ContainsKey(e.PropertyName))
                {
                    this.TryAttachEnumeratedPropertyChangeTracker(this._enumerablePropertiesToTrack[e.PropertyName], currentValue);
                }

                this.OnInputsChanged();
            }
        }

        /// <summary>
        /// Fires when any property of an element of a collection that is being tracked is fired
        /// </summary>
        /// <param name="sender">Object raising the event</param>
        /// <param name="e">Event arguments</param>
        private void ResultsInputViewModelBase_TrackedElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            this.OnInputsChanged();
        }

        /// <summary>
        /// Fires when a collection of a tracked property changes
        /// </summary>
        /// <param name="sender">Object raising the event</param>
        /// <param name="e">Event arguments</param>
        private void ResultsInputViewModelBase_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // we need to attach the property changed etc of the new items
            if (e.NewItems != null)
            {
                foreach (var element in e.NewItems)
                {
                    if (element is ResultsInputViewModelBase)
                    {
                        ((ResultsInputViewModelBase)element).InputsChanged += this.InputsChanged;
                    }
                    else if (element is INotifyPropertyChanged)
                    {
                        ((INotifyPropertyChanged)element).PropertyChanged += new PropertyChangedEventHandler(this.ResultsInputViewModelBase_TrackedElementPropertyChanged);
                    }
                }
            }

            // we should stop tracking the items leaving the collection
            if (e.OldItems != null)
            {
                foreach (var element in e.OldItems)
                {
                    if (element is ResultsInputViewModelBase)
                    {
                        ((ResultsInputViewModelBase)element).InputsChanged -= this.InputsChanged;
                    }
                    else if (element is INotifyPropertyChanged)
                    {
                        ((INotifyPropertyChanged)element).PropertyChanged -= new PropertyChangedEventHandler(this.ResultsInputViewModelBase_TrackedElementPropertyChanged);
                    }
                }
            }

            this.OnInputsChanged();
        }
    }
This class essentially adds internal listeners to all the different kinds of events that the derived class may raise that we are interested in. It leaves one single property that must be implemented in the derived class, where the programmer can list the properties which should be tracked. For example:

protected override string[] TrackedInputProperties
        {
            get { return new string[] {
                "AgeBandFilters",
                "MaritalStatusFilters"
            }; }
        }
Finally, in the "Results" ViewModel, I pass in an instance of my derived class and then listen for the "InputsChanged" event:

public ExampleInputViewModel ExampleInputViewModel
        {
            get { return _exampleInputViewModel; }
            set {
                //track changes in input configs to invalidate results
                RemoveInputChangedHandler(_exampleInputViewModel);
                _exampleInputViewModel = value;
                AddInputChangedHandler(_exampleInputViewModel);

                RaisePropertyChanged("ExampleInputViewModel"); }
        }

 private void RemoveInputChangedHandler(ResultsInputViewModelBase inputConfigViewModel)
        {
            if(inputConfigViewModel != null)
                inputConfigViewModel.InputsChanged -= new EventHandler(inputConfigViewModel_InputsChanged);
        }

        private void AddInputChangedHandler(ResultsInputViewModelBase inputConfigViewModel)
        {
            if (inputConfigViewModel != null)
                inputConfigViewModel.InputsChanged += new EventHandler(inputConfigViewModel_InputsChanged);
        }

        void inputConfigViewModel_InputsChanged(object sender, EventArgs e)
        {
                //invalidate results
                CurrentResults = null;
        }