2010-11-10 106 views
26

UPDATE:問題已解決,請參閱my Answer爲嵌套(子)對象訂閱INotifyPropertyChanged

我在尋找一個乾淨,優雅的解決處理INotifyPropertyChanged事件嵌套(子)對象。示例代碼:

public class Person : INotifyPropertyChanged { 

    private string _firstName; 
    private int _age; 
    private Person _bestFriend; 

    public string FirstName { 
    get { return _firstName; } 
    set { 
     // Short implementation for simplicity reasons 
     _firstName = value; 
     RaisePropertyChanged("FirstName"); 
    } 
    } 

    public int Age { 
    get { return _age; } 
    set { 
     // Short implementation for simplicity reasons 
     _age = value; 
     RaisePropertyChanged("Age"); 
    } 
    } 

    public Person BestFriend { 
    get { return _bestFriend; } 
    set { 
     // - Unsubscribe from _bestFriend's INotifyPropertyChanged Event 
     // if not null 

     _bestFriend = value; 
     RaisePropertyChanged("BestFriend"); 

     // - Subscribe to _bestFriend's INotifyPropertyChanged Event if not null 
     // - When _bestFriend's INotifyPropertyChanged Event is fired, i'd like 
     // to have the RaisePropertyChanged("BestFriend") method invoked 
     // - Also, I guess some kind of *weak* event handler is required 
     // if a Person instance i beeing destroyed 
    } 
    } 

    // **INotifyPropertyChanged implementation** 
    // Implementation of RaisePropertyChanged method 

} 

專注於BestFriend屬性和它的價值設定器。現在我知道我可以手動執行此操作,執行評論中描述的所有步驟。但是這將會是很多代碼,特別是當我計劃擁有許多像這樣實現INotifyPropertyChanged的子屬性時。當然,它們並不總是相同的類型,它們唯一共同的地方就是INotifyPropertyChanged接口。

原因是,在我的真實場景中,我有一個複雜的「項目」(在購物車中)對象,它具有在多個圖層上嵌套對象屬性(Item有一個「License」對象,它本身可以有子對象再次),我需要得到有關「項目」的任何單一更改的通知才能重新計算價格。

你有些好的提示,甚至有些 執行幫我解決 這個?

不幸的是,我不能/允許使用PostSharp之類的構建後步驟來實現我的目標。

非常感謝你提前,
- 托馬斯

+0

AFAIK,大多數綁定實現不希望事件以這種方式傳播。畢竟,你*還沒有*改變過BestFriend的價值。 – 2010-11-10 10:09:41

回答

19

因爲我找不到現成的解決方案,所以我根據Pieters(和Marks)的建議(謝謝!)完成了一個自定義實現。

中的類,你會被通知在一個很深的對象樹的任何變化,這適用於任何INotifyPropertyChanged實施類型和INotifyCollectionChanged *實現集合(很顯然,我使用的是ObservableCollection爲)。

我希望這是一個相當乾淨和優雅的解決方案,雖然沒有經過充分測試,並且還有增強空間。這是很容易使用,使用它的靜態Create方法,並把您的INotifyPropertyChanged剛剛創建的ChangeListener一個實例:

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

PropertyChangedEventArgs提供PropertyName這將是永遠的對​​象的完整「路徑」。例如,如果您更改人物的「最佳朋友」名稱,則PropertyName將爲「最佳朋友名稱」,如果BestFriend有一組兒童,並且您更改了其年齡,則值將爲「最佳朋友。兒童[]。年齡」等等。當你的對象被銷燬時,不要忘記Dispose,那麼它將(希望)完全取消所有事件監聽器的訂閱。

它在.NET(測試4)和Silverlight(測試4)編譯。由於分隔在三個類的代碼,我已經張貼代碼gist 705450在那裏你可以抓住這一切:https://gist.github.com/705450 **

*)的原因之一,該代碼工作是,ObservableCollection也實現INotifyPropertyChanged,否則根據需要它不會工作,這是一個已知警告

**)免費使用,在MIT License

+0

非常有幫助。 thx :) – Marco 2013-07-10 11:10:07

+1

我冒昧地將你的要點包裝到nuget包中:https://www.nuget.org/packages/RecursiveChangeNotifier/ – LOST 2016-09-30 05:11:24

+0

謝謝@LOST,希望它能幫助某人 – thmshd 2016-09-30 05:24:00

16

我想你要尋找的是類似WPF結合。

如何INotifyPropertyChanged作品就是RaisePropertyChanged("BestFriend");必須被fored財產BestFriend變化時。不是當對象本身發生任何變化時。

你將如何實現這個是通過兩步INotifyPropertyChanged事件處理程序。您的聽衆將在Person的變化事件中註冊。 BestFriend設置/更改後,您將註冊BestFriendPerson的更改事件。然後,您開始監聽該對象的更改事件。

這正是WPF綁定實現這一點的方式。通過該系統監聽嵌套對象的更改。

當你在Person中實現它時,這個不起作用的原因是水平可能變得非常深,並且BestFriend的變化事件不再意味着什麼(「發生了什麼變化?」)。當你有循環關係時,這個問題就會變大。你單身的最好的朋友是你最好的惡魔的母親。然後,當其中一個屬性發生變化時,會發生堆棧溢出。

所以,你將如何解決這個問題是創建一個類,你可以建立監聽器。例如,你會在BestFriend.FirstName上建立一個監聽器。然後,該課程將在發生變化的事件Person上放置事件處理程序,並聽取BestFriend上的更改。然後,如果發生變化,它會將聽衆放在BestFriend上,並聽取FirstName的更改。然後,當它發生變化時,它會發出一個事件,然後您可以聽到該事件。這基本上是WPF綁定的工作原理。

有關WPF綁定的更多信息,請參閱http://msdn.microsoft.com/en-us/library/ms750413.aspx

+0

感謝您的回答,我沒有真正意識到您描述的一些問題。目前,只有**取消訂閱**纔會導致一些頭痛。例如,可以將'BestFriend'設置爲'null'。是否真的有可能以這種方式取消訂閱,而沒有實施「INotifyPropertyChanging」類? – thmshd 2010-11-10 19:35:55

+0

當偵聽器(我描述的對象)獲得對'Person'的第一個引用時,它將引用複製到'BestFriend'並向該引用註冊一個偵聽器。如果'BestFriend'發生變化(例如變爲'null'),則它首先從複製的引用中斷開事件,複製新引用(可能爲null)並在其上註冊事件處理程序(如果它不是null)。這裏的訣竅是,您絕對需要將引用複製到監聽器,而不是使用'Person'的'BestFriend'屬性。這應該可以解決你的問題。 – 2010-11-10 19:57:46

+0

非常好,我會盡力實現這一點,並在完成後發佈解決方案。 +1票至少對你=) – thmshd 2010-11-10 21:26:55

3

有趣的解決方案托馬斯釋放。

我找到了另一種解決方案。它被稱爲傳播者設計模式。您可以在網上找到更多(例如,在CodeProject上:Propagator in C# - An Alternative to the Observer Design Pattern)。

基本上,它是更新依賴網絡中對象的模式。當狀態變化需要通過對象網絡推送時,它非常有用。狀態變化是通過傳播者傳播網絡中的對象本身來表示的。通過將狀態變化封裝爲對象,傳播者變得鬆散耦合。

可重複使用的傳播器類的類圖:閱讀來自CodeProject

A class diagram of the re-usable Propagator classes

+0

+1感謝您的貢獻,我一定要仔細看看 – thmshd 2012-01-26 20:01:00

0

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

 /// <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

我試過添加這個,但我得到名稱'一次性'不存在於這種情況下。什麼是一次性的? – 2012-10-01 22:54:05

+0

Disposable是Reactive擴展中的具體幫助類,它創建實現具有各種行爲的IDisposable的具體對象。你可以刪除那些代碼,並且如果你現在不想學習如果IDisposable的快樂(儘管它值得付出),那麼你可以明確地解除事件。 – DanH 2012-10-05 11:00:02

0

我一直在在網上搜索一天,我發現從薩沙理髮另一個很好的解決方案:

http://www.codeproject.com/Articles/166530/A-Chained-Property-Observer

他創造了一個鏈接的屬性觀察內弱引用。如果你想看另一種實現此功能的好方法,請查看文章。

而且我也想提一個很好的實現與無擴展@ http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/

此解決方案的工作只爲觀察員的一個級別,觀察員的不是一個完整的鏈條。

+0

感謝您的更新。薩沙的解決方案顯然是最先進的,而我可以記得我的工作也很好,無論如何,這是我一段時間沒有碰過的話題:) – thmshd 2014-05-12 10:41:00

0

退房我在CodeProject上的解決方案: http://www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator 這不正是你所需要的 - 幫助傳播(以優雅的方式)相關的屬性時在這個或任何嵌套視圖模型相關的依賴性改變:

public decimal ExchTotalPrice 
{ 
    get 
    { 
     RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice)); 
     RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate)); 
     return TotalPrice * ExchangeRate.Rate; 
    } 
}