根据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 不更新为“新值”
我要实现的目标:
当我直接在ViewModel上设置测试值 时,我可以轻松完成这项工作-但这似乎不正确,并且与我认为可以构造应用程序/代码的地方相去甚远。未来的目标是拥有一个拥有大部分数据(并从API,本地数据库或仅从内存中获取数据)的Singleton(我认为静态无法工作)“ RecordStore”
所以问题是:
为什么NotifyPropertyChange不冒泡到View / ViewModel?
还是我没有看到另一个问题?
我已经读过INotifyPropertyChanged bubbling in class hierarchy 和 What 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-interface 和 OnPropertyChange 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 不会冒泡,就像我希望的那样,最好直接绑定到嵌套对象。
答案 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}"/>