我有一个Person类:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name{
get { return _name; }
set {
if ( _name != value ) {
_name = value;
OnPropertyChanged( "Name" );
}
}
private Address _primaryAddress;
public Address PrimaryAddress {
get { return _primaryAddress; }
set {
if ( _primaryAddress != value ) {
_primaryAddress = value;
OnPropertyChanged( "PrimaryAddress" );
}
}
//OnPropertyChanged code goes here
}
我有一个地址类:
public class Address : INotifyPropertyChanged
{
private string _streetone;
public string StreetOne{
get { return _streetone; }
set {
if ( _streetone != value ) {
_streetone = value;
OnPropertyChanged( "StreetOne" );
}
}
//Other fields here
//OnPropertyChanged code goes here
}
我有一个ViewModel:
public class MyViewModel
{
//constructor and other stuff here
private Person _person;
public Person Person{
get { return _person; }
set {
if ( _person != value ) {
_person = value;
OnPropertyChanged( "Person" );
}
}
}
我有一个View,其中包含以下几行:
<TextBox Text="{Binding Person.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
<TextBox Text="{Binding Person.Address.StreetOne, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
视图加载时,两个值都显示在文本框中。
对第一个文本框的更改将在MyViewModel中触发OnPropertyChanged( "Person" )
。大。
对第二个文本框("Person.Address.StreetOne")
的更改不会在MyViewModel中触发OnPropertyChanged( "Person" )
。这意味着它不会调用Person对象的SET方法。不是很好。有趣的是,调用了Address类中的StreetOne的SET方法。
当Person.Address.StreetOne
发生变化时,如何在ViewModel中获取Person对象的SET方法?
我是否需要展平我的数据,以便SteetOne在Person内部而不是Address ??
谢谢!
答案 0 :(得分:12)
添加&#39;传递&#39; ViewModel的属性是一个很好的解决方案,很快就会变得站不住脚。标准的替代方案是传播如下变化:
public Address PrimaryAddress {
get => _primaryAddress;
set {
if ( _primaryAddress != value )
{
//Clean-up old event handler:
if(_primaryAddress != null)
_primaryAddress.PropertyChanged -= AddressChanged;
_primaryAddress = value;
if (_primaryAddress != null)
_primaryAddress.PropertyChanged += AddressChanged;
OnPropertyChanged( "PrimaryAddress" );
}
void AddressChanged(object sender, PropertyChangedEventArgs args)
=> OnPropertyChanged("PrimaryAddress");
}
}
现在,更改通知会从“地址”传播到“人”。
编辑:将处理程序移至c#7本地函数。
答案 1 :(得分:8)
如果您想要调用viewmodel SET,则可以创建街道属性
public class MyViewModel
{
//constructor and other stuff here
public string Street{
get { return this.Person.PrimaryAddress.StreetOne; }
set {
if ( this.Person.PrimaryAddress.StreetOne!= value ) {
this.Person.PrimaryAddress.StreetOne = value;
OnPropertyChanged( "Street" );
}
}
}
XAML
<TextBox Text="{Binding Street, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged />
但这种解决方案有其缺点。我在我的项目中使用Reeds答案
答案 2 :(得分:5)
当Person.Address.StreetOne发生变化时,如何在ViewModel中获取Person对象的SET方法?
你为什么要这样做?它不应该是必需的 - 您只需要触发StreetOne
属性更改事件。
我是否需要展平我的数据,以便SteetOne在Person内部而不是Address ??
如果你想真正导致它被触发,你不需要压扁它(虽然这是一个选项)。您可以在Person类中订阅Address
的{{1}}事件,并在PropertyChanged
更改时为“地址”引发事件。但是,这不是必要的。
答案 3 :(得分:1)
由于我无法找到即用型解决方案,我已经完成了基于Pieters(和Marks)建议的自定义实现(谢谢!)。
使用这些类,您将收到有关深层对象树中任何更改的通知,这适用于任何INotifyPropertyChanged
实现类型和INotifyCollectionChanged
*实现集合(显然,我正在使用{{ 1}}为了那个)。
我希望这是一个非常干净和优雅的解决方案,虽然没有经过全面测试,但仍有增强空间。它非常易于使用,只需使用静态ObservableCollection
方法创建ChangeListener
实例并传递Create
:
INotifyPropertyChanged
var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged +=
new PropertyChangedEventHandler(listener_PropertyChanged);
提供PropertyChangedEventArgs
,它始终是对象的完整“路径”。例如,如果您更改人员的“BestFriend”名称,PropertyName
将为“BestFriend.Name”,如果PropertyName
有一个子集合并且您更改了它的年龄,则值将为“ BestFriend.Children []。年龄“等等。当您的对象被销毁时,不要忘记BestFriend
,然后它(希望)完全取消订阅所有事件监听器。
它在.NET(测试4)和Silverlight(测试4)中编译。由于代码分为三个类,我已将代码发布到 gist 705450 ,您可以将其全部抓取:https://gist.github.com/705450 **
*)代码工作的一个原因是Dispose
也实现了ObservableCollection
,否则它将无法正常工作,这是一个众所周知的警告
**)免费使用,在MIT License下发布
答案 4 :(得分:0)
您的财产更改通知中存在拼写错误:
OnPropertyChanged( "SteetOne" );
应该是
OnPropertyChanged( "StreetOne" );