Click here to Skip to main content
15,892,005 members
Articles / Desktop Programming / Windows Forms

A Chained Property Observer

Rate me:
Please Sign up or sign in to vote.
4.96/5 (42 votes)
8 Mar 2011CPOL26 min read 163.7K   854   97  
Set of utility classes to observe a chain of INotifyPropertyChanged objects.
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;

using ChainedObserver.WPFTest.DataHelper;
using CommonModel;


namespace ChainedObserver.WPFTest
{
    /// <summary>
    /// A simple ViewModel that demonstrates how the <c>ChainObserver</c> can be used
    /// within a typical ViewModel. This ViewModel provides a lot of superflous properties
    /// such as LastChanges/PersonChanges/AddressChanges/CityChanges for the View, that you 
    /// would not typically need, but they are simply here to aid the demo
    /// </summary>
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        #region Data
        private ObservableCollection<string> personChanges = new ObservableCollection<string>();
        private ObservableCollection<string> addressChanges = new ObservableCollection<string>();
        private ObservableCollection<string> geoLocationChanges = new ObservableCollection<string>();
        private ObservableCollection<string> lastChanges = new ObservableCollection<string>();
        private ChainPropertyObserver chainPropertyObserver;
        #endregion

        #region Ctor
        public MainWindowViewModel()
        {
            MyPerson = new Person();
            MyPerson.Age = 12;
            MyPerson.Name = "sacha";
            MyPerson.Address = new Address()
            {
                Addressline1 = "21 maple street",
                Addressline2 = "Hanover",
                GeoLocation = new GeoLocation { Longitude=0.5, Latitude=0.5 }
            };

            //create chainPropertyObserver
            chainPropertyObserver = new ChainPropertyObserver();
            chainPropertyObserver.CreateChain(() => MyPerson.Address.GeoLocation, Changed);
           
            //And now hook up some specific Chain Listeners
            chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson,
                () =>
                {
                    personChanges.Add(MyPerson.ToString());
                });

            chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson.Address,
                () =>
                {
                    addressChanges.Add(MyPerson.Address.ToString());
                });
            
            chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson.Address.GeoLocation,
                () =>
                {
                    geoLocationChanges.Add(MyPerson.Address.GeoLocation.ToString());

                });

            //setup commands
            ChangePersonCommand = new SimpleCommand<object,object>(ExecuteChangePersonCommand);
            ChangeAddressCommand = new SimpleCommand<object, object>(ExecuteChangeAddressCommand);
            ChangeGeoLocationCommand = new SimpleCommand<object, object>(ExecuteChangeGeoLocationCommand);
        }
        #endregion

        #region Private Methods
        private void Changed(string propertyThatChanged)
        {
            lastChanges.Add(propertyThatChanged);
        }
        #endregion

        #region Public Properties

        public ICommand ChangePersonCommand { get; private set; }
        public ICommand ChangeAddressCommand { get; private set; }
        public ICommand ChangeGeoLocationCommand { get; private set; }

        public Person MyPerson { get; set; }

        public ObservableCollection<string> LastChanges
        {
            get { return lastChanges; }
        }

        public ObservableCollection<string> PersonChanges
        {
            get { return personChanges; }
        }

        public ObservableCollection<string> AddressChanges
        {
            get { return addressChanges; }
        }

        public ObservableCollection<string> GeoLocationChanges
        {
            get { return geoLocationChanges; }
        }
        #endregion

        #region Commands

        /// <summary>
        /// Do a simple one property change to Person
        /// which should cause a single "Person" change
        /// </summary>
        private void ExecuteChangePersonCommand(object parameter)
        {
            MyPerson.Name = Jabberwock.RandomString();
        }

        /// <summary>
        /// Show that the <c>ChainedObserver</c> is even ok with you setting new
        /// objects (Address in this case) in the chain, it will be fine with that and pick up
        /// the new object (Address in this case) just fine, and monitor the changes in that
        /// using existing change handlers that were set up when the <c>ChainedObserver</c>
        /// was 1st created
        /// 
        /// This command also changes a number of properties on a new Address just so that
        /// the user can see that the <c>ChainedObserver</c> is indeed ok with new objects
        /// being introduced into the chain.
        /// </summary>
        private void ExecuteChangeAddressCommand(object parameter)
        {
            Address currentAddress = MyPerson.Address;
            GeoLocation currentGeoLocation = MyPerson.Address.GeoLocation;
            //Should fire in this order
            //1. MyPerson change, due to setting new Address on Person
            //2. Address change, due to setting new Addressline1 on new Address (which starts out with empty property values)
            //3. Address change, due to setting new Addressline2 on new Address (which starts out with empty property values)
            //4. Address change, due to setting new Addressline3 on new Address (which starts out with empty property values)

            //NOTE : That since we are effectively reusing the existing GeoLocation that the ChainedObserver already knows about
            //       assigning this to the new Address does not constitute a change, as IT IS THE SAME object, so no change

            MyPerson.Address = new Address();
            MyPerson.Address.Addressline1 = Jabberwock.RandomString();
            MyPerson.Address.Addressline2 = currentAddress.Addressline2;
            MyPerson.Address.Addressline3 = currentAddress.Addressline3;
            MyPerson.Address.GeoLocation = currentGeoLocation;


        }


        /// <summary>
        /// Do a simple one property change to GeoLocation
        /// which should cause a single "GeoLocation" change
        /// </summary>
        private void ExecuteChangeGeoLocationCommand(object parameter)
        {
            MyPerson.Address.GeoLocation.Longitude = Jabberwock.RandomDouble();
        }
        #endregion

        #region INotifyPropertyChanged event

        ///<summary>
        ///Occurs when a property value changes.
        ///</summary>
        public event PropertyChangedEventHandler PropertyChanged;


        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event for
        /// a given property.
        /// </summary>
        /// <param name="propertyName">The name of the changed property.</param>
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions