2010-11-10 50 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; 
    } 
}