2009-08-26 37 views
16

這個問題是要告訴我缺乏的預期行爲的理解執行時/使用INotifyPropertyChanged的:當實現INotifyPropertyChanged的嵌套屬性必須改變父對象傳播嗎?

的問題是 - 結合按預期方式工作,當你有它本身實現INotifyPropertyChanged一類,具有嵌套INotifyPropertyChanged類型的屬性是否需要內部訂閱以更改這些屬性的通知,然後傳播通知?或者,綁定基礎架構預計會有這麼多的智慧嗎?

例如(注意此代碼是不完整的 - 只是爲了說明問題):

public class Address : INotifyPropertyChanged 
    { 
     string m_street 
     string m_city; 

     public string Street 
     { 
      get { return m_street; } 
      set 
      { 
      m_street = value; 
      NotifyPropertyChanged(new PropertyChangedEventArgs("Street")); 
      } 
     } 

     public string City 
     { 
      get { return m_city; } 
      set 
      { 
      m_city = value; 
      NotifyPropertyChanged(new PropertyChangedEventArgs("City")); 
      } 
     } 

    public class Person : INotifyPropertyChanged 
    { 
     Address m_address; 

     public Address 
     { 
      get { return m_address = value; } 
      set 
      { 
      m_address = value; 
      NotifyPropertyChanged(new PropertyChangedEventArgs("Address")); 
      } 
     } 
    } 

因此,在這個例子中,我們已經有了一個Person對象嵌套地址對象。兩者都實現INotifyPropertyChanged,以便更改其屬性將導致將屬性更改通知傳輸給訂戶。

但讓我們假設使用綁定某人正在訂閱更改Person對象上的通知,並且正在偵聽Address屬性的更改。如果地址屬性本身發生更改(分配了不同的地址對象),它們將收到通知,但如果嵌套地址對象(城市或街道)包含的數據發生更改,則不會收到通知。

這導致了一個問題 - 綁定基礎架構是否期望處理此問題,還是應該在我的Person實現中訂閱更改地址對象上的通知,然後將它們作爲「Address」的更改傳播?

如果你明白了這一點,謝謝你只是花時間閱讀這個冗長的問題?

評論非常感謝!

菲爾

+0

我在google搜索後發現了這個問題。對我來說,似乎你必須手動訂閱childrens PropertyChanged事件並將其泡泡到WPF綁定中。 – loraderon 2010-11-03 12:25:27

+1

loraderon,我敢肯定情況並非如此 - 至少在我的測試中,事實證明是這樣的。而且,沒有任何信息(我已經找到)另有說明。你有任何可以提供的信息的鏈接嗎?謝謝。菲爾 – Phil 2010-11-03 20:56:06

+0

我也沒有任何鏈接。在我目前的項目中,我不得不冒泡PropertyChanged事件來使其工作。我是WPF和MVVM的新手,所以它可能只是我的項目特別的東西。 – loraderon 2010-11-04 07:24:04

回答

1

當你說

...說使用綁定有人 訂閱改變 Person對象通知,

有人在你回答了這個問題訂閱Person並無法知道Address是否已更改。 所以你必須自己處理這種情況(這很容易實現)。

+1

這是真的嗎?例如在WPF中,我可以這樣做 <標籤內容= 「城市」/> <文本框的文本= {結合Address.City} /> 這裏(如果我是正確的!),綁定基礎結構將確保城市和街道文本框更新如果地址屬性更改或街道/城市變化。 – Phil 2009-08-26 19:41:44

+0

對不起,xaml在評論中出現得並不太好。無論如何,我想說的是它可以是調用者(使用Person對象的實體)在person對象和調用者使用的任何嵌套屬性對象上註冊更改通知的要求*。 我不是說這就是這種情況!這就是爲什麼我問原始問題,因爲我相信這是可能的兩種設計(將責任推送給用戶或實施者)。 我試過看MS文檔,但沒有發現任何明確的。 乾杯! – Phil 2009-08-26 19:48:01

+0

我很抱歉,假設您使用Winfomrs。我對WPF知之甚少,但是,我的猜測是,在WPF中它也會以完全相同的方式工作。 WPF確實具有泡沫事件的概念,您可能必須利用這一事實。 看看這篇文章,它應該解釋你創建自定義路由事件 http://msdn.microsoft.com/en-us/library/ms742806.aspx – 2009-08-27 03:56:10

2

一來做到這一點是一個事件處理程序添加到人,將處理來自m_address對象通知事件最簡單的方法:

public class Person : INotifyPropertyChanged 
{ 
    Address m_address; 

    public Address 
    { 
     get { return m_address = value; } 
     set 
     { 
     m_address = value; 
     NotifyPropertyChanged(new PropertyChangedEventArgs("Address")); 
     m_address.PropertyChanged += new PropertyChangedEventHandler(AddressPropertyChanged); 
     } 
    } 
    void AddressPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     NotifyPropertyChanged(new PropertyChangedEventArgs("Address")) 
    } 
} 
+2

tkola,我知道如何爲兒童實施財產變更通知。問題是,數據綁定是否需要正確工作?根據我所看到的,答案似乎是否定的 - 當子對象發生更改時,您不需要執行更改通知:它已實際上已由綁定基礎結構處理(至少對於WPF) – Phil 2010-08-02 03:49:00

+10

您可能想要取消訂閱在將地址集中的m_address設置爲新值之前,先執行m_address的PropertyChanged事件。 – 2010-10-14 13:04:17

+0

如果Address是一個DependencyProperty,該怎麼辦? – tofutim 2011-06-30 22:22:31

0

如果你想子對象看,如果他們是一部分他們的父母直接需要自己冒泡。

對於您的示例,您將綁定到視圖中的「Address.Street」,因此您需要冒泡包含該字符串的notifypropertychanged。

我寫了一個簡單的幫手來做到這一點。你只需在父視圖模型構造函數中調用BubblePropertyChanged(x => x.BestFriend)。注:有一個假設,你有一個方法稱爲NotifyPropertyChanged在你的父母,但你可以適應它來適應。

 /// <summary> 
    /// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping 
    /// the naming hierarchy in place. 
    /// This is useful for nested view models. 
    /// </summary> 
    /// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param> 
    /// <returns></returns> 
    public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> property) 
    { 
     // This step is relatively expensive but only called once during setup. 
     MemberExpression body = (MemberExpression)property.Body; 
     var prefix = body.Member.Name + "."; 

     INotifyPropertyChanged child = property.Compile().Invoke(); 

     PropertyChangedEventHandler handler = (sender, e) => 
     { 
      this.NotifyPropertyChanged(prefix + e.PropertyName); 
     }; 

     child.PropertyChanged += handler; 

     return Disposable.Create(() => { child.PropertyChanged -= handler; }); 
    } 
0

一個老問題,不過...

我最初的方法是附加子屬性變爲父。這有一個優點,消費父母的事件很容易。只需要訂閱父母。

public class NotifyChangedBase : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    readonly Dictionary<string, AttachedNotifyHandler> attachedHandlers = new Dictionary<string, AttachedNotifyHandler>(); 

    [NotifyPropertyChangedInvocator] 
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    protected void AttachPropertyChanged(INotifyPropertyChanged notifyPropertyChanged, 
     [CallerMemberName] string propertyName = null) 
    { 
     if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); 
     // ReSharper disable once ExplicitCallerInfoArgument 
     DetachCurrentPropertyChanged(propertyName); 
     if (notifyPropertyChanged != null) 
     { 
      attachedHandlers.Add(propertyName, new AttachedNotifyHandler(propertyName, this, notifyPropertyChanged)); 
     } 
    } 

    protected void DetachCurrentPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); 
     AttachedNotifyHandler handler; 
     if (attachedHandlers.TryGetValue(propertyName, out handler)) 
     { 
      handler.Dispose(); 
      attachedHandlers.Remove(propertyName); 
     } 
    } 

    sealed class AttachedNotifyHandler : IDisposable 
    { 
     readonly string propertyName; 
     readonly NotifyChangedBase currentObject; 
     readonly INotifyPropertyChanged attachedObject; 

     public AttachedNotifyHandler(
      [NotNull] string propertyName, 
      [NotNull] NotifyChangedBase currentObject, 
      [NotNull] INotifyPropertyChanged attachedObject) 
     { 
      if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); 
      if (currentObject == null) throw new ArgumentNullException(nameof(currentObject)); 
      if (attachedObject == null) throw new ArgumentNullException(nameof(attachedObject)); 
      this.propertyName = propertyName; 
      this.currentObject = currentObject; 
      this.attachedObject = attachedObject; 

      attachedObject.PropertyChanged += TrackedObjectOnPropertyChanged; 
     } 

     public void Dispose() 
     { 
      attachedObject.PropertyChanged -= TrackedObjectOnPropertyChanged; 
     } 

     void TrackedObjectOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) 
     { 
      currentObject.OnPropertyChanged(propertyName); 
     } 
    } 
} 

的用法很簡單:

public class Foo : NotifyChangedBase 
{ 
    Bar bar; 

    public Bar Bar 
    { 
     get { return bar; } 
     set 
     { 
      if (Equals(value, bar)) return; 
      bar = value; 
      AttachPropertyChanged(bar); 
      OnPropertyChanged(); 
     } 
    } 
} 

public class Bar : NotifyChangedBase 
{ 
    string prop; 

    public string Prop 
    { 
     get { return prop; } 
     set 
     { 
      if (value == prop) return; 
      prop = value; 
      OnPropertyChanged(); 
     } 
    } 
} 

然而,這種方法不是很靈活,有沒有對其進行控制,至少在沒有額外的複雜的工程。如果訂閱系統具有遍歷嵌套數據結構的靈活性,則它的適用性僅限於一級兒童。

儘管注意事項可能是可以接受的,但根據使用情況,我已經離開這種方法,因爲它永遠不能確定數據結構如何最終被使用。目前傾向於解決方案,比如這一個:

https://github.com/buunguyen/notify

這樣,即使是複雜的數據結構是簡單的和可預測的,它是用戶控制下如何訂閱和如何反應,它結合發動機的能力打得很好。