19

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

我有一个视图模型:

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

}

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

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

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

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

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

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

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

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

谢谢!

4

5 回答 5

18

虽然向 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 本地函数。

于 2016-03-11T17:04:10.827 回答
10

如果你想调用 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" );
       }
   }

 }

xml

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

但这种解决方案有其缺点。我在我的项目中选择 Reeds 答案

于 2012-08-09T05:21:03.057 回答
6

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

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

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

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

于 2012-08-08T17:43:52.247 回答
3

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

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

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

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

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

它在 .NET(在 4 中测试)和 Silverlight(在 4 中测试)编译。因为代码分为三个类,我已将代码发布到gist 705450,您可以在其中获取所有内容:https ://gist.github.com/705450 **

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

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

于 2016-03-10T11:58:23.377 回答
0

您的属性更改通知中有拼写错误:

OnPropertyChanged( "SteetOne" );

应该

OnPropertyChanged( "StreetOne" );

于 2012-08-08T20:53:14.173 回答