wpf 为嵌套属性实现 INotifyPropertyChanged

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11870069/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 04:54:23  来源:igfitidea点击:

Implementing INotifyPropertyChanged for nested properties

c#wpfmvvmbindinginotifypropertychanged

提问by lloyd christmas

I have a Person class:

我有一个 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
}

I have an Address class:

我有一个地址类:

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
}

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

}

I have a View which has the following lines:

我有一个视图,其中包含以下几行:

<TextBox  Text="{Binding Person.Name, Mode=TwoWay,   
    UpdateSourceTrigger=PropertyChanged />

<TextBox  Text="{Binding Person.Address.StreetOne, Mode=TwoWay,   
    UpdateSourceTrigger=PropertyChanged />

Both values show up in the text box ok when the view loads.

当视图加载时,两个值都显示在文本框中。

Changes to the first text box will fire OnPropertyChanged( "Person" )in MyViewModel. Great.

对第一个文本框的更改将OnPropertyChanged( "Person" )在 MyViewModel 中触发。伟大的。

Changes to the second text box ("Person.Address.StreetOne")does NOT fire OnPropertyChanged( "Person" )inside MyViewModel. Meaning it doesn't call the Person object's SET method. Not great. Interestingly the SET method of StreetOne inside the Address class is called.

对第二个文本框的更改("Person.Address.StreetOne")不会OnPropertyChanged( "Person" )在 MyViewModel 内触发。这意味着它不会调用 Person 对象的 SET 方法。不是很好。有趣的是,调用了 Address 类中 StreetOne 的 SET 方法。

How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOneis changed???

如何在Person.Address.StreetOne更改时调用 ViewModel 中的 Person 对象的 SET 方法???

Do I need to flatten my data so SteetOne is inside Person and not Address??

我是否需要展平我的数据,以便 SteetOne 在 Person 而不是 Address 中?

Thanks!

谢谢!

采纳答案by blindmeis

if you want the viewmodel SET to be called you could create a street property

如果你想调用 viewmodel SET 你可以创建一个 street 属性

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

xml

<TextBox  Text="{Binding Street, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged />

but this solution has its drawbacks. i go with Reeds answer in my projects

但是这个解决方案有它的缺点。我在我的项目中使用 Reeds 答案

回答by Andrew Hanlon

While adding 'pass-through' properties to your ViewModel is a fine solution, it can quickly become untenable. The standard alternative is to propagate changes as below:

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

Now change notifications are propagated from Address to person.

现在更改通知从地址传播到人。

Edit:Moved handler to c# 7 local function.

编辑:将处理程序移至 c# 7 本地函数。

回答by Reed Copsey

How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???

当 Person.Address.StreetOne 更改时,如何获取要调用的 ViewModel 中 Person 对象的 SET 方法???

Why do you want to do this? It should not be required - you only need the StreetOneproperty changed event to fire.

你为什么要这样做?它不应该是必需的 - 您只需要StreetOne触发属性更改事件。

Do I need to flatten my data so SteetOne is inside Person and not Address??

我是否需要展平我的数据,以便 SteetOne 在 Person 而不是 Address 中?

If you want to actually cause this to trigger, you don't need to flatten it (though that is an option). You can subscribe to the Address's PropertyChangedevent within your Person class, and raise the event for "Address" within Personwhen it changes. This shouldn't be necessary, however.

如果您想真正触发它,则不需要将其展平(尽管这是一个选项)。您可以在 Person 类中订阅Address'sPropertyChanged事件,并在“地址”Person更改时引发该事件。然而,这不应该是必要的。

回答by Revious

Since I wasn't able to find a ready-to-use solution, I've done a custom implementation based on Pieters (and Marks) suggestions (thanks!).

由于我无法找到现成的解决方案,因此我根据 Pieters(和 Marks)的建议(谢谢!)进行了自定义实现。

Using the classes, you will be notified about any change in a deep object tree, this works for any INotifyPropertyChangedimplementing Types and INotifyCollectionChanged* implementing collections (Obviously, I'm using the ObservableCollectionfor that).

使用这些类,您将收到有关深层对象树中任何更改的通知,这适用于任何INotifyPropertyChanged实现类型和INotifyCollectionChanged* 实现集合(显然,我正在ObservableCollection为此使用 )。

I hope this turned out to be a quite clean and elegant solution, it's not fully tested though and there is room for enhancements. It's pretty easy to use, just create an instance of ChangeListenerusing it's static Createmethod and passing your INotifyPropertyChanged:

我希望这是一个非常干净和优雅的解决方案,虽然它没有经过全面测试,但仍有改进的空间。它非常易于使用,只需创建一个ChangeListener使用它的静态Create方法的实例并传递您的INotifyPropertyChanged

var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged += 
    new PropertyChangedEventHandler(listener_PropertyChanged);

the PropertyChangedEventArgsprovide a PropertyNamewhich will be always the full "path" of your Objects. For example, if you change your Persons's "BestFriend" Name, the PropertyNamewill be "BestFriend.Name", if the BestFriendhas a collection of Children and you change it's Age, the value will be "BestFriend.Children[].Age" and so on. Don't forget to Disposewhen your object is destroyed, then it will (hopefully) completely unsubscribe from all event listeners.

PropertyChangedEventArgs提供PropertyName这将是永远的对象的完整“路径”。例如,如果您更改 Persons 的“BestFriend”名称,PropertyName则将是“BestFriend.Name”,如果您BestFriend有一个 Children 集合并且您更改了它的 Age,则该值将是“BestFriend.Children[].Age”等在。不要忘记Dispose当您的对象被销毁时,它会(希望)完全取消订阅所有事件侦听器。

It compiles in .NET (Tested in 4) and Silverlight (Tested in 4). Because the code in seperated in three classes, I've posted the code to gist 705450where you can grab it all: https://gist.github.com/705450**

它在 .NET(在 4 中测试)和 Silverlight(在 4 中测试)中编译。因为代码分三类,我把代码贴到了gist 705450,大家可以下载:https: //gist.github.com/705450**

*) One reason that the code is working is that the ObservableCollectionalso implements INotifyPropertyChanged, else it wouldn't work as desired, this is a known caveat

*) 代码正常工作的一个原因是ObservableCollection也实现了INotifyPropertyChanged,否则它不会按预期工作,这是一个已知的警告

**) Use for free, released under MIT License

**) 免费使用,在MIT 许可发布

回答by Monroe Thomas

There is a spelling mistake in your property change notification:

您的房产变更通知中有拼写错误:

OnPropertyChanged( "SteetOne" );

OnPropertyChanged( "SteetOne" );

should be

应该

OnPropertyChanged( "StreetOne" );

OnPropertyChanged( "StreetOne" );