冒泡NotifyPropertyChanged

时间:2018-07-30 22:20:03

标签: c# wpf

根据this answer,我不必为NotifyPropertyChanges烦恼而冒充层次结构,但仍然无法使其与像这样的(简化的test-)结构一起工作:

数据保存类

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"));
            }
        }
    }
}

使用该Test-Class和Test-Property的ViewModel:

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
            }
        }
    }
}

最后是我的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!
    }
}

在xaml中,除了那个按钮外,我还有一个简单的TextBlock,它绑定到ViewModel的Test属性:

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

现在发生的事情:

  • 默认值“ default”显示在TextBlock中。

  • 当我单击按钮时,消息框将显示“新值”

  • TextBlock 更新为“新值”

我要实现的目标:

  • 似乎很简单:TextBlock应该更新为“新值”

当我直接在ViewModel上设置测试值 时,我可以轻松完成这项工作-但这似乎不正确,并且与我认为可以构造应用程序/代码的地方相去甚远。未来的目标是拥有一个拥有大部分数据(并从API,本地数据库或仅从内存中获取数据)的Singleton(我认为静态无法工作)“ RecordStore”

所以问题是:
为什么NotifyPropertyChange不冒泡到View / ViewModel?
还是我没有看到另一个问题?

我已经读过INotifyPropertyChanged bubbling in class hierarchyWhat is a good way to bubble up INotifyPropertyChanged events through ViewModel properties with MVVM?https://docs.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interfaceOnPropertyChange called but not taking any effect on UI

其中大多数问题还很老...

编辑:
我以这种方式尝试了@MineR的建议:

// 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}"/>

不幸的是,现在我什至没有得到默认值,所以我一定误会了。

EDIT2
我在第一次编辑中做错了一件事情:

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

我完成了TNC,删除了本地Test参数,直接绑定到Path=TNC.Test

所以我知道,PropertyChanges 不会冒泡,就像我希望的那样,最好直接绑定到嵌套对象。

1 个答案:

答案 0 :(得分:2)

“冒泡”是routed events的概念。像PropertyChanged这样的常规事件不会“冒泡”。

除了ViewModel中的明显错误tnc = tnc;(应该为this.tnc = tnc;)之外,这两个类的Test属性也不相关。为了更新其自己的Test属性,ViewModel必须在tnc注册一个PropertyChanged事件处理程序。并且当其自己的Test属性更改时,它必须更新tnc的属性。

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
            }
        }
    }
}

或者,删除Test属性的后备字段,仅在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; }
    }
}

幸运的是,根本没有必要。

相反,可能只是像这样的公共Tnc属性

public class ViewModel
{
    public TestNotifyChanged Tnc { get; }

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

具有这样的绑定:

<TextBlock Text="{Binding Tnc.Test}"/>