簡體   English   中英

為嵌套屬性實現 INotifyPropertyChanged

[英]Implementing INotifyPropertyChanged for nested properties

我有一個 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 />

當視圖加載時,這兩個值都顯示在文本框中。

對第一個文本框的更改將在 MyViewModel 中觸發 OnPropertyChanged OnPropertyChanged( "Person" ) 偉大的。

對第二個文本框("Person.Address.StreetOne")的更改不會在 MyViewModel 中觸發 OnPropertyChanged OnPropertyChanged( "Person" ) 這意味着它不調用 Person 對象的 SET 方法。 不是很好。 有趣的是,Address 類中 StreetOne 的 SET 方法被調用了。

Person.Address.StreetOne更改時,如何獲取 ViewModel 中 Person 對象的 SET 方法?

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

謝謝!

雖然向 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 本地函數。

如果你想調用 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 答案

當 Person.Address.StreetOne 更改時,如何獲取 ViewModel 中 Person 對象的 SET 方法?

你為什么要這樣做? 它不應該是必需的 - 您只需要StreetOne屬性更改事件。

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

如果您想真正觸發它,則不需要將其展平(盡管這是一個選項)。 您可以在 Person 類中訂閱AddressPropertyChanged事件,並在Person更改時引發“Address”事件。 然而,這不應該是必要的。

由於我無法找到現成的解決方案,因此我根據 Pieters(和 Marks)的建議(謝謝!)進行了自定義實現。

使用這些類,您將收到有關深層對象樹中任何更改的通知,這適用於任何INotifyPropertyChanged實現類型和INotifyCollectionChanged * 實現集合(顯然,我為此使用了ObservableCollection )。

我希望這是一個非常干凈和優雅的解決方案,雖然它沒有經過全面測試並且還有改進的空間。 它非常易於使用,只需使用它的靜態Create方法創建一個ChangeListener實例並傳遞您的INotifyPropertyChanged

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

PropertyChangedEventArgs提供一個PropertyName ,它將始終是您的對象的完整“路徑”。 例如,如果您更改 Persons 的“BestFriend”名稱, PropertyName將為“BestFriend.Name”,如果BestFriend有一個 Children 集合並且您更改它的 Age,則該值將為“BestFriend.Children[].Age”等等。 當您的對象被銷毀時,不要忘記Dispose ,然后它將(希望)完全取消訂閱所有事件偵聽器。

它在 .NET(在 4 中測試)和 Silverlight(在 4 中測試)編譯。 因為代碼分為三個類,我已將代碼發布到gist 705450 ,您可以在其中獲取所有內容: https ://gist.github.com/705450 **

*) 代碼工作的一個原因是ObservableCollection還實現了INotifyPropertyChanged ,否則它不會按預期工作,這是一個已知的警告

**) 免費使用,在MIT 許可下發布

您的屬性更改通知中有拼寫錯誤:

OnPropertyChanged( "SteetOne" );

應該

OnPropertyChanged( "StreetOne" );

請查看EverCodo.ChangesMonitoring 這是一個框架,用於處理嵌套對象和集合的任意層次結構上的 PropertyChanged 和 CollectionChanged 事件。

創建一個監視器來處理對象樹的所有更改事件:

_ChangesMonitor = ChangesMonitor.Create(Root);
_ChangesMonitor.Changed += ChangesMonitor_Changed;

對對象樹進行任意修改(所有這些都將被處理):

Root.Children[5].Children[3].Children[1].Metadata.Tags.Add("Some tag");
Root.Children[5].Children[3].Metadata = new Metadata();
Root.Children[5].Children[3].Metadata.Description = "Some description";
Root.Children[5].Name = "Some name";
Root.Children[5].Children = new ObservableCollection<Entity>();

在一個地方處理所有事件:

private void ChangesMonitor_Changed(object sender, MonitoredObjectChangedEventArgs args)
{
    // inspect args parameter for detailed information about the event
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM