SireChicken
SireChicken

Reputation: 55

OnPropertyChanged 2nd level update

Searching didn't bring me any clues and I am sort of at a loss. WPF is self taught so far, so I might be overlooking something simple.

<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

<TextBlock Text={Binding BoundTextProperty}"/>

that's the simplified xml

public class MainViewModel
{
    private Model Data;
    public MainViewModel()
    {...}
    public string BoundTextProperty => Data.BoundTextProperty;
    ...
}

The Property that's bound referencing the Property holding the Data in the model

public class Model : INotifyPropertyChanged
{
    private long number;
    public long Number
    {
        get { return number; }
        set 
        {
            number = value;
            OnPropertyChanged(nameof(BoundTextProperty));
        }
    }

    public string BoundTextProperty => $"Some text {Number} some text again";

    public virtual event PropertyChangedEventHandler PropertyChanged;

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

I swear it worked at some point. The string has a couple other variables, but that's the basic of how it works or rather doesn't.

My Question is wether or not the Binding can actually bubble up, and if it can, why doesn't it?

Upvotes: 0

Views: 324

Answers (2)

mm8
mm8

Reputation: 169200

You need to raise the PropertyChanged event for the source property that you bind to in your XAML. In this case you bind to the BoundTextProperty of the MainViewModel which means that the MainViewModel class should raise the PropertyChanged event.

It doesn't matter whether the source property wraps another property of a class that does raise the PropertyChanged event. It's the source object of the binding that notifies the view.

You could also just bind to the "model" property directly, provided that you turn Data into a public property in your view model:

public Model Data { get; private set; }
...
<TextBlock Text="{Binding Data.BoundTextProperty}"/>

If you choose to stick with your wrapper property, the MainViewModel must implement the INotifyPropertyChanged event and raise the PropertyChanged event whenever the model is updated:

public class MainViewModel : INotifyPropertyChanged
{
    private readonly Model Data;

    public MainViewModel()
    {
        Data = new Model();
        Data.PropertyChanged += Data_PropertyChanged;
    }

    private void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged("BoundTextProperty");
    }

    public string BoundTextProperty => Data.BoundTextProperty;

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

Upvotes: 1

FrankM
FrankM

Reputation: 1107

You have to add the code for bubbling up the Model's PropertyChanged event from the ViewModel to the View. Here is an example (based on your code):

public class MainViewModel : ViewModelBase
{
    private readonly Model Data;

    public MainViewModel()
    {
        Data = new Model();
        Data.PropertyChanged += ModelOnPropertyChanged;
    }

    private void ModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case nameof(Model.BoundTextProperty):
                OnPropertyChanged(nameof(MainViewModel.BoundTextProperty));
                break;
            // add cases for other properties here:
        }
    }

    public string BoundTextProperty => Data.BoundTextProperty;
}

public class Model : ModelBase
{
    private long number;
    public long Number
    {
        get { return number; }
        set
        {
            number = value;
            OnPropertyChanged(nameof(BoundTextProperty));
        }
    }

    public string BoundTextProperty => $"Some text {Number} some text again";

}

public abstract class ViewModelBase : Base
{
    // add other ViewModel related stuff here
}


public abstract class ModelBase : Base
{
    // add other Model related stuff here
}

public abstract class Base : INotifyPropertyChanged
{
    public virtual event PropertyChangedEventHandler PropertyChanged;

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

Upvotes: 1

Related Questions