Click here to Skip to main content
15,889,096 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
public class Person : INotifyPropertyChanged
{
    private string name;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Name
    {
        get { return name; }
        set
        {
            object before = Name;
            name = value;
            OnPropertyChanged("Name", before, Name);
        }
    }

    void OnPropertyChanged(PropertyChangedEventArgs e, object? argvaluebefore = null, object? argnewvalue = null, [CallerMemberName] string argcallername = "", [CallerLineNumber] int argcallerline = 0)
	{
        //How can I inject argvaluebefore and argvalue here ?
		
		PropertyChanged?.Invoke(this, e);
	}
}



public class Master
{

	Person _person = new();


	public Master()
	{
	
		_person.PropertyChanged += MyHandler;
	
	
	}
	

	private void MyHandler(object? sender, PropertyChangedEventArgs e)
	{

		//How do I retrieve the curent values of property Name here plus the value before ?

	}


Any Ideas how I can receive curent value of the property Person.Name plus the value before in Instance of class Master ?

What I have tried:

Did not find a solution.
The idea was to store the values temprarily in Person and read it from Master but this did not work.
Posted

If you want to see the value before it changed, use the INotifyPropertyChanging interface and raise the event before you assign the new value. Both of these events have a name property that tells you which property changed.
C#
public class SomeViewModel : INotifyPropertyChanged, INotifyPropertyChanging
{
  public event PropertyChangedEventHandler PropertyChanged;
  public event PropertyChangingEventHandler PropertyChanging;

  private string name;
  public string Name
  {
    get => name;
    set
    {
      PropertyChanging?.Invoke(this, nameof(Name));
      name = value;
      PropertyChanged?.Invoke(this, nameof(Name));
    }
  }
}

public class MyView : IDisposable
{
  private SomeViewModel viewModel = new();
  private List<string> audit = new();
  public MyView()
  {
    viewModel.PropertyChanging += HandlePropertyChanging;
    viewModel.PropertyChanged += HandlePropertyChanged;
  }

  public void Dispose()
  {
    viewModel.PropertyChanging -= HandlePropertyChanging;
    viewModel.PropertyChanged -= HandlePropertyChanged;
  }
  private void HandlePropertyChanging(object sender, PropertyChangingEventArgs e)
  {
    audit.Add($"[PropertyChanging] for Name = {viewModel.Name}");
  }

  private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    audit.Add($"[PropertyChanged] for Name = {viewModel.Name}");
  }
}
What's happening here is that you never pass the value across. Instead, you rely on the fact that the property change process notifies you via events so you can get the current value when the PropertyChanging event is fired, and then get the new value when the PropertyChanged event has been raised.

[Edit]Based on your new requirements, you will need to create a new event that is capable of handling the old and new values. You will need to raise both the new event, and the PropertyChanged event if your code relies on the PropertyChanged for binding. Your event might look something like this:
C#
public class LogChangeEventArgs : EventArgs
{
  public string PropertyName { get; set; }
  public object? OldValue { get; set; }
  public object? NewValue { get; set; }
}

public class SomeViewModel : INotifyPropertyChanged
{
  public event EventHandler<LogChangeEventArgs> LogEvents;
  public event PropertyChangedEventHandler PropertyChanged;

  private string name;
  public string Name
  {
    get => name;
    set
    {
      RaiseLogEvents(nameof(Name), name, value);
      name = value;
      PropertyChanged?.Invoke(this, nameof(Name));
    }
  }
  private void RaiseLogEvents(string propertyName, object? oldValue, object? newValue)
  {
    LogEvents?.Invoke(this, new LogChangeEventArgs(){ Name = propertName, OldValue = oldValue, NewValue = newValue});
  }
}
Then, all you need do is hook up the handler in the class you want to consume this from.
 
Share this answer
 
v3
Comments
AtaChris 8-Apr-24 3:30am    
Thank you for this approach which would definitifely solve the Problem.
Maybe you can have a look to my comment @ Solution 2.
I would like to avoid to do value comparisons etc. in my Master class.
I need this feature for logging purposes.
My goal is to have a looger that shows namespace + class + property + old and new value of property when events in classes are fired.
The logger invocation should be located in the event handler of my Master class.
And I would like to hand over the info I got from sub classes directly to the logger.
Pete O'Hanlon 8-Apr-24 4:11am    
I have updated the answer based on your new requirements.

I would suggest that your Master class has an event handler that handles the Person class Name property changed event. Something along these lines.


C#
using System.ComponentModel;
using System.Runtime.CompilerServices;

public class Program
{
    public static void Main()
    {
        Person person=new() { Name="Bob"};
        Master master= new();
        //subscribe master to person's property changed event
        person.PropertyChanged += master.PersonPropertyChangedHandler;
        //raise the event
        person.Name = "Jim";
        Console.ReadLine();
    }
}
public class Person : INotifyPropertyChanged
{
    public string? OldName { get; set; }
    private string? name;
    public string? Name
    {
        get { return name; }
        set
        {
            OldName = Name;
            name = value;
            OnPropertyChanged();
        }
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

public class Master
{
    public void PersonPropertyChangedHandler(object? sender, PropertyChangedEventArgs e)
    {

        Person? person = sender as Person ?? throw new ArgumentNullException(nameof(sender));
        //All the Person public properties are now in scope
        Console.WriteLine($"Old name is {person.OldName}. The current naame is {person.Name}");

    }
} 
 
Share this answer
 
Comments
AtaChris 8-Apr-24 3:14am    
Thank you, this seems to be a solution that answers the question.
The only problem I see is the following:
My class Person and other classes not listed here carry many properties of different types.

This means I would have provide for every class a seperate event handler in Master class and I would have to ckeck each property of the classes in these event handlers. Another problem is, that I would have to compare old and new value in these event handlers to find out if they have changed.

I am looking for a solution where PropertyChangedEventHandler? sends the class and property name as well as its old and new value to a more generic PropertyChangedHandler in Master Class which is responsible for all Sub classes, so that I can retrieve the information I want from sender and/or e wihtout any forther validations in Master classes PropertyChangedHandler ?

In other words: is there a way to "inject" oldvalue and new value into
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

So that all info I need is already prepared and send by Person class.
If you mean this code:
C#
public string Name
{
    get { return name; }
    set
    {
        object before = Name;
        name = value;
        OnPropertyChanged("Name", before, Name);
    }
}
And that you want to be able to access the value in before after you have set the Name property, then you can't, not like that. Everything in C# has a scope: a lifetime after which it is no longer available. For variables, that scope is the set of curly brackets in which it is declared. Once execution exits that scope the variable is no longer accessible, and has effectively been destroyed. So in your code the scope is like this:
C#
public string Name
{
    get { return name; }
    set
    { // before exists
        object before = Name;
        name = value;
        OnPropertyChanged("Name", before, Name);
    } // before is destroyed.
}
So it is not possible to ever access the previous value outside that specific property setter.

However, if you change it slightly:
C#
public string Name
{
    get { return name; }
    set
    {
        Before = Name;
        name = value;
        OnPropertyChanged("Name", before, Name);
    }
public string Before { get; private set; }
}
Then it's scope becomes the scope of the property, and it is accessible as long as the class instance exists.
 
Share this answer
 
The PropertyChangedEventArgs Class[^] does not provide any way to pass the previous and current values of the property.

If you want to access those values in an event handler, then you would need to create your own event args class - for example:
C#
public class PropertyValueChangedEventArgs : PropertyChangedEventArgs
{
    public PropertyValueChangedEventArgs(string? propertyName, object? previousValue, object? newValue) : base(propertyName)
    {
        PreviousValue = previousValue;
        NewValue = newValue;
    }
    
    public object? PreviousValue { get; }
    public object? NewValue { get; }
}
You would then either pass an instance of this class to the PropertyChanged event, and let the caller cast the event args to this class:
C#
private void Person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e is PropertyValueChangedEventArgs a)
    {
        Console.WriteLine($"{a.PropertyName} changed from {a.PreviousValue} to {a.NewValue}");
    }
    else
    {
        Console.WriteLine($"{a.PropertyName} changed.");
    }
}
Or you could create a separate PropertyValueChanged event to make the event args type explicit:
C#
public delegate void PropertyValueChangedEvent(object sender, PropertyValueChangedEventArgs e);
C#
public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public event PropertyValueChangedEventHandler PropertyValueChanged;
    
    private void SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = default)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue)) return;
        
        T oldValue = field;
        field = newValue;
        
        PropertyValueChangedEventArgs e = new(propertyName, oldValue, newValue);
        PropertyChanged?.Invoke(this, e);
        PropertyValueChanged?.Invoke(this, e);
    }

    private string name;
    
    public string Name
    {
        get { return name; }
        set { SetProperty(ref name, value); }
    }
}
C#
private void Person_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
    Console.WriteLine($"{e.PropertyName} changed from {e.PreviousValue} to {e.NewValue}");
}
 
Share this answer
 
Comments
AtaChris 8-Apr-24 12:21pm    
Richards solution is what i was looking for.
Thank to you Richard an everybody else for the help !!!

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



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900