1

According to this answer I should not need to bother about NotifyPropertyChanges Bubbling up the hierachie, still I can't get it to work with a (simplified test-) structure like that:

a Data-Holding Class

public class TestNotifyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _Test = "default";
    public string Test
    {
        get
        {
            return _Test;
        }
        set
        {
            if(_Test!=value)
            {
                _Test = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Test"));
            }
        }
    }
}

A ViewModel that used that Test-Class and Test-Property:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private TestNotifyChanged tnc = new TestNotifyChanged(); // only to init, otherwise VS screams at me

    public ViewModel(TestNotifyChanged tnc)
    {
        tnc = tnc; // getting an instance of TestNotifyChanged from "Master" passed in, which hopefully will be replaces by a singleton class.
    }

    private string _Test;
    public string Test
    {
        get
        {
            return tnc.Test;  // this might be the crucial part!?
        }
        set
        {
            if (_Test != value) // never hits that, as I would expect, but you never know..
            {
                _Test = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Test"));  // of course also never hit, as expected
            }
        }
    }
}

And finally my MainWindow cs

public partial class MainWindow : Window
{
    TestNotifyChanged tnc;
    public MainWindow()
    {
        InitializeComponent();
        tnc = new TestNotifyChanged();
        DataContext = new ViewModel(tnc); // pass in my Test-Object that has the Values.
    }

    private void ButtonGet_Click(object sender, RoutedEventArgs e)
    {
        tnc.Test = "new Value";
        MessageBox.Show($"{tnc.Test}"); // got "new Value" here!
    }
}

And in xaml I have besides that one button a simple TextBlock that is bound to the ViewModel's Test Property:

 <TextBlock x:Name="OutputId" Text="{Binding Path=Test, Mode=OneWay}"/>

What is happening now:

  • The default value "default" is shown in TextBlock.

  • When I click the button the messageBox shows the "new Value"

  • TextBlock is not updating to "new Value"

What I want to achieve:

  • seems easy: TextBlock should update to "new Value"

I can easily make this work when I directly set the Test Value on the ViewModel - but this doesn't seem right and is far away from what I thought I could structure my app/code. The future goal is to have a Singleton (static won't work I figured out) "RecordStore" that has most of the data (and gets it from an API, from local Database, or just from Memory if any of these are done)

So the question is:
Why is the NotifyPropertyChange not bubbling up to the View/ViewModel?
Or is there another issue I don't see?

I've read INotifyPropertyChanged bubbling in class hierarchy and What is a good way to bubble up INotifyPropertyChanged events through ViewModel properties with MVVM? and https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface and OnPropertyChange called but not taking any effect on UI

most of those questions are also quite old...

EDIT:
I tried @MineR 's suggestion that way:

// made tnc public in ViewModel
public TestNotifyChanged tnc = new TestNotifyChanged();

// changed Binding directly to that (and it's Property):
<TextBlock x:Name="OutputId" Text="{Binding Path=tnc.Test, Mode=OneWay}"/>

Unfortunately now I don't even get the default, so I must have misunderstood smth.

EDIT2:
I did one thing wrong in the 1st edit:

// this isn't recognized as bindable parameter:
public TestNotifyChanged tnc = new TestNotifyChanged();
// it instead has to be
public TestNotifyChanged tnc { get; }

And I made it TNC, removed the local Test parameter, bound directly to Path=TNC.Test

So I understood, that PropertyChanges do not bubble up the way I hoped/thought, it's better to bind directly down to the nested object.

Jeff
  • 6,895
  • 1
  • 15
  • 33
  • In your ViewModel, `_Test = value;` should actually be `tnc.Test = value;`? – Sach Jul 30 '18 at 22:35
  • @Sach ah, ok. I'll try that! - but `tnc.Text` is the source which could be the only one hitting the `set{}` - which is never hit anyway!? - _after trying_: nope, nothing changed. – Jeff Jul 30 '18 at 22:36
  • No bubbling is done at all in the code you have. If you want to make it work without bubbling, you must make tnc public, and bind to tnc.Test - that's what's done in the first question you referenced. Or you can bubble it by subscribing to tnc.PropertyChanged and raising PropertyChanged when tnc.PropertyChanged is raised. If you can get away with the first option, do that - it's simpler. – MineR Jul 30 '18 at 23:51
  • @MineR unfortunately I cant get it done with the first option. Could you give me an example of second option? (although I'd prefer the first option....would make much more sense) – Jeff Jul 31 '18 at 00:20
  • 1
    If you make tnc public, it should also be a property to be binded: `public TestNotifyChanged tnc {get;set;}`. It works for me, but then the `Test` property in your VM is not used any more, so it's not really 'bubbling'. – P.Manthe Jul 31 '18 at 01:18

1 Answers1

2

"Bubbling" is a concept of routed events. A regular event like PropertyChanged doesn't "bubble up".

Besides the apparent bug tnc = tnc; in the ViewModel (which should be this.tnc = tnc;) the Test properties of the two classes are unrelated. In order to update its own Test property, ViewModel must register a PropertyChanged event handler at tnc. And it must update the property of tnc when its own Test property changes.

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private TestNotifyChanged tnc;

    public ViewModel(TestNotifyChanged t)
    {
        tnc = t;
        tnc.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(Test) || string.IsNullOrEmpty(e.PropertyName))
            {
                Test = tnc.Test; // update ViewModel.Test from TestNotifyChanged.Test
            }
        };
    }

    private string test;
    public string Test
    {
        get
        {
            return test; // always return own value
        }
        set
        {
            if (test != value)
            {
                test = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Test)));

                tnc.Test = Test; // update TestNotifyChanged.Test from ViewModel.Test
            }
        }
    }
}

Alternatively, drop the Test property's backing field and only operate on tnc.Test:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private TestNotifyChanged tnc;

    public ViewModel(TestNotifyChanged t)
    {
        tnc = t;
        tnc.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(Test) || string.IsNullOrEmpty(e.PropertyName))
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Test)));
            }
        };
    }

    public string Test
    {
        get { return tnc.Test; }
        set { tnc.Test = Test; }
    }
}

Fortunately, it is not necessary at all.

There could instead just be a public Tnc property like

public class ViewModel
{
    public TestNotifyChanged Tnc { get; }

    public ViewModel(TestNotifyChanged tnc)
    {
        Tnc = tnc;
    }
}

with a Binding like this:

<TextBlock Text="{Binding Tnc.Test}"/>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • If we use your first answer, which is *real bubbling* according to my understanding, wouldn't it be a bit tricky to bubble multiple properties? – P.Manthe Jul 31 '18 at 05:34
  • That isn't bubbling. And it's tricky because you have to take care for attaching and detaching PropertyChanged event handlers. And as said, its fortunately not necessary at all. – Clemens Jul 31 '18 at 05:36
  • 1
    Thank you for that comprehensive answer! I kindof tried the second version before, but made a crucial misstake. I had `public TestNotifyChanged Tnc = new TestNofifyChanged();`, so xaml didn't recognize it as bindable property. – Jeff Jul 31 '18 at 11:01