2

I have this code where I have my ViewModel and the ViewModel has a property where it gets all of its properties.

This is rough pseudo-code:

public class MyClassViewModel : INotifyPropertyChanged
{

    public MyClassViewModel ()
    {

    }

    public BaseClass myClassBase { get ; set; }

    public string Title
    {
        get
        {
            return myClassBase.Title;
        }
        set
        {
            myClassBase.Title = value;
            RaisePropertyChanged("Title");
        }
    }

    public string Description
    {
        get
        {
            return myClassBase.Description;
        }
        set
        {
            myClassBase.Description = value;
            RaisePropertyChanged("Description");
        }
    }
}

And this is the BaseClass:

public class BaseClass
{
    public BaseClass()
    {

    }

    public string Title {get;set;}
    public string Description {get;set;}
}

CheckItemViewModel is the one binded to UI. So if I do something like MyClassViewModel .Title = "Test"; it properly refreshes the UI.

However, I need to do something like MyClassViewModel.myClassBase.Title = "Test" for specific reasons (Javascript - Chakra interface). The problem with this then is that the UI does not Refresh anymore since it doesn't have RaisePropertyChanged.

Even when I implemented RaisePropertyChanged inside the BaseClass itself, it still doesn't work. It doesn't work because PropertyChanged in BaseClass is always null.

I suspect it's because MyClassViewModel is the one binded to UI. So PropertyChanged in BaseClass is never binded.

Is there a way to trigger the Parent's RaisePropertyChanged?

Thank you

Andre Hofmeister
  • 3,185
  • 11
  • 51
  • 74
Water
  • 1,114
  • 1
  • 15
  • 31

2 Answers2

2

I would suggest implementing INotifyPropertyChanged on both classes, then have MyClassViewModel subscribe to the event in BaseClass and forward it to the UI:

public class MyClassViewModel : INotifyPropertyChanged, IDisposable
{
    private BaseClass myClassBase;

    public void Dispose()
    {
        if (myClassBase != null) myClassBase.PropertyChanged -= OnBaseClassPropertyChanged;
    }

    public BaseClass MyClassBase {
        get {
            return myClassBase;
        }

        set {
            if (myClassBase != null) myClassBase.PropertyChanged -= OnBaseClassPropertyChanged;
            myClassBase = value;
            myClassBase.PropertyChanged += OnBaseClassPropertyChanged;
        }
    }

    private void OnBaseClassPropertyChanged(object sender, PropertyChangedEventArgs args) {
        RaisePropertyChanged(args.PropertyName);
    }

    // forwarded properties (Title and Description) go here
}
casablanca
  • 69,683
  • 7
  • 133
  • 150
2

First of all, you can simplify the RaisePropertyChanged this way:

public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

So you don't need to write RaisePropertyChanged("Description"), but only: RaisePropertyChanged(), and the propertyName is automatically injected. That's awesome if you refactor frequently: you don't have to deal with the nightmare of remembering all the "Title" and "Description" strings in the whole solution :)

Second, if the BaseClass has the PropertyChangedEvent, you can listen to it in the MyClassViewModel.

myClassBase.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };

But, if you don't inject myClassBase immediately in the constructor of MyClassViewModel, or if the myClassBase can change sometime, things get a bit more complicated.

You have to make MyClassViewModel also to implement INotifyPropertyChanging:

public event PropertyChangingEventHandler PropertyChanging;

public void RaisePropertyChanging([CallerMemberName] string propertyName = null)
{
    PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}

You have to raise notifications also for the myClassBase:

public BaseClass myClassBase
{
    get { return _myClassBase; }
    set
    {
        RaisePropertyChanging();
        _myClassBase = value;
        RaisePropertyChanged();
    }
}
private BaseClass _myClassBase;

Then, all you need is this code:

public MyClassViewModel()
{
    PropertyChanging += OnPropertyChanging;
    PropertyChanged += OnPropertyChanged;
}

private void OnPropertyChanging(object sender, PropertyChangingEventArgs e)
{
    if (e.PropertyName != nameof(MyClassViewModel.myClassBase))
        return; //or do something with the other properties

    if (myClassBase == null)
        return;

    myClassBase.PropertyChanged -= OnMyBaseClassPropertyChanged;
}

private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName != nameof(MyClassViewModel.myClassBase))
        return; //or do something with the other properties

    if (myClassBase == null)
        return;

    myClassBase.PropertyChanged += OnMyBaseClassPropertyChanged;
}

private void OnMyBaseClassPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    RaisePropertyChanged(e.PropertyName);
}

NB: I use the C#-6.0 nameof() operator, I hope you can use it, it's simply awesome!

EDIT:

Here you have a simple test method that demonstrates the correct functionality:

    [TestMethod]
    public void ChildClassPropertyChanged()
    {
        var bc = new BaseClass();

        var c = new MyClassViewModel();

        bc.Title = "t1";

        c.myClassBase = bc;

        Assert.AreEqual("t1", c.Title);

        c.Title = "t2";
        Assert.AreEqual("t2", c.Title);

        c.myClassBase.Title = "t3";
        Assert.AreEqual("t3", c.Title);

        c.myClassBase = new BaseClass();

        bc.Title = "t4";
        Assert.AreEqual(null, c.Title);

        c.myClassBase.Title = "t5";
        Assert.AreEqual("t5", c.Title);
    }

Keep in mind that if you set a null myClassBase, inside your properties' getters and setters the code throws a NullReferenceException. Maybe you should modify it this way:

public string Title
{
    get
    {
        return myClassBase?.Title;
    }
    set
    {
        if (myClassBase != null)
                myClassBase.Title = value;
        RaisePropertyChanged();
    }
}
Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47