7

I need to capture old value, new value and property name in PropertyChanged event handler. The default implementation of INotifyPropertyChanged provides only name of the property. So after searching on internet, I found similar question here with Extended implementation of property changed.

Below is the link to that Q and interface declaration:

NotifyPropertyChanged event where event args contain the old value

public interface INotifyPropertyChangedExtended<T>
{
    event PropertyChangedExtendedEventHandler<T> PropertyChanged;
}

public delegate void PropertyChangedExtendedEventHandler(object sender, PropertyChangedExtendedEventArgs e);

Above interface will solve my problem, however I am not understanding how to implement generic interface on my Entity class because T will change depending on data type of property.

Can anyone help me in understanding, will it be possible to implement this interface with T parameter. Sample implementation of this will be really helpful.

Thanks in advance for help.

Umesh

EDIT #1: Based on the reply from Peter, posting updated code that may be useful for someone who wants to capture old and new values in PropertyChanged event.

    public class PropertyChangedExtendedEventArgs : PropertyChangedEventArgs
{
    public string OldValue { get; set; }

    public string NewValue { get; set; }

    public PropertyChangedExtendedEventArgs(string propertyName, string oldValue, string newValue) : base(propertyName)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}

    //
// Summary:
//     Notifies clients that a property value has changed.
public interface INotifyPropertyChangedEnhanced
{
    //
    // Summary:
    //     Occurs when a property value changes.
    event PropertyChangedEventHandlerEnhanced PropertyChanged;
}

public delegate void PropertyChangedEventHandlerEnhanced(object sender, PropertyChangedExtendedEventArgs e);

public abstract class BindableBase : INotifyPropertyChangedEnhanced
{
    public event PropertyChangedEventHandlerEnhanced PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] 
    string propertyName = null)
    {
        if (Equals(storage, value))
        {
            return false;
        }

        var oldValue = storage;
        storage = value;
        this.OnPropertyChanged(oldValue, value, propertyName);
        return true;
    }
    protected void OnPropertyChanged<T>(T oldValue, T newValue, [CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedExtendedEventArgs(propertyName, oldValue?.ToString(), newValue?.ToString()));
    }
}
Umesh Kanase
  • 83
  • 1
  • 5

4 Answers4

8

Just Implement your own PropertyChangedEventArgs.

Implemeting a new Interface not necessary.

This solutions works. Here is one of the benefits of polymorphism

public class PropertyChangedExtendedEventArgs : PropertyChangedEventArgs
{
        public virtual object OldValue { get; private set; }
        public virtual object NewValue { get; private set; }

        public PropertyChangedExtendedEventArgs(string propertyName, object oldValue, 
               object newValue)
               : base(propertyName)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }
    }

public class NotifyObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName, object oldvalue, object newvalue)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedExtendedEventArgs(propertyName, oldvalue, newvalue));
        }
    }



public class Person : NotifyObject
    {
        private int _name;
        public int Name
        {
            get { return _name; }
            set
            {
                var oldValue = _name;
                _name = value;
                NotifyPropertyChanged("Name", oldValue, value);

            }
        }
    }

And your view model

Person person = new Person();
person.PropertyChanged += person_PropertyChanged;

  void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var args = e as PropertyChangedExtendedEventArgs;
            //do whatever you want
        }
Erhan Urun
  • 228
  • 3
  • 9
2

I am not understanding how to implement generic interface on my Entity class because T will change depending on data type of property

That's correct. The answer you're looking at isn't actually all that good an answer, because it contains code that doesn't compile or make sense.

The basic idea of raising the non-generic PropertyChanged event by passing the generic args class that inherits the non-generic PropertyChangedEventArgs class is by itself okay. It should work fine in your case.

But! You're not going to be able to implement the generic interface that answer proposes, because (as you already note) the interface doesn't work when the properties have different types. In fact, the answer you're looking at wouldn't even compile with that interface, because their event-raising method is itself generic, and raises the non-generic PropertyChanged event (the method would be incompatible with the generic interface), and they don't bother to actually implement their generic interface (if they'd tried, they'd have noticed that the generic method doesn't work with the generic interface…the method's type parameter T is different from the interface's type parameter T).

It's unfortunate that highly up-voted answer was written that way. It has some useful ideas in it, but it also has completely unworkable ideas, and is going to just confuse exactly the people who are looking for an answer like that (i.e. the very people who most want to read and use the answer).

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks Peter. Instead of using T, I solved it by using string type for old and new value; as for auditing I need to capture old and new value of each property and store it in DB for reporting purpose. – Umesh Kanase Dec 10 '17 at 03:37
0

You can't. Implement INotifyPropetyChanging as well. That will provide access to the old value. You can find example implementations for both interfaces in the documentation.

Andy
  • 8,432
  • 6
  • 38
  • 76
0

As @Andy mentioned, you can implement INotifyPropetyChanging to get the old value, but storing is a big cumbersome, so I made a helper class NotifyPropertyChangingCache you can use as following:

class EntityBase : INotifyPropertyChanged, INotifyPropertyChanging
{ /* ... */ }

// initiating the entity
var entity = new EntityBase();

// initiating the cache:
var notifyPropertyChangingCache = new Ice1e0.ComponentModel.NotifyPropertyChangingCache();
entity.PropertyChanging += notifyPropertyChangingCache.OnPropertyChanging;

// your property changed event looks like this
entity.PropertyChanged += (s, e) =>
{
    var oldValue = notifyPropertyChangingCache.GetLastPropertyValue(e.PropertyName);

    // your code goes here ...
};

// when you are done with your entity, don't forget to remove the events (we don't want to create a memory leak ...)
entity.PropertyChanging -= notifyPropertyChangingCache.OnPropertyChanging;
//entity.PropertyChanged -= ... (I assume you use not a in-line method here as I did in this example)
ice1e0
  • 947
  • 7
  • 15