订阅嵌套(子)对象的 INotifyPropertyChanged

发布于 2024-10-01 16:56:12 字数 1748 浏览 2 评论 0原文

我正在寻找一个干净且优雅的解决方案来处理嵌套(子)对象的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 接口。

原因是,在我的真实场景中,我有一个复杂的“项目”(在购物车中)对象,该对象在多个层上嵌套了对象属性(项目有一个“许可证”对象,它本身可以再次拥有子对象)并且我需要收到有关“商品”的任何单一更改的通知才能重新计算价格。

你有什么好的建议吗? 实施帮我解决 这个?

不幸的是,我无法/不允许使用 PostSharp 等构建后步骤来实现我的目标。

I'm looking for a clean and elegant solution to handle the INotifyPropertyChanged event of nested (child) objects. Example code:

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

}

Focus on the BestFriend Property and it's value setter. Now I know that I could do this manually, implementing all steps described in the comments. But this is going to be a lot of code, especially when I'm planning to have many child properties implementing INotifyPropertyChanged like this. Of course they are not going to be always of same Type, the only thing they have in common is the INotifyPropertyChanged interface.

The reason is, that in my real scenario, I have a complex "Item" (in cart) object which has nested object properties over several layers (Item is having a "License" object, which can itself have child objects again) and I need to get notified about any single change of the "Item" to be able to recalculate the price.

Do you some good tips or even some
implementation to help me to solve
this?

Unfortunately, I'm not able/allowed to use post-build steps like PostSharp to accomplish my goal.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

伤感在游骋 2024-10-08 16:56:12

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

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

我希望这是一个非常干净和优雅的解决方案,尽管它还没有经过充分测试,并且还有增强的空间。它非常容易使用,只需使用其静态 Create 方法创建一个 ChangeListener 实例并传递您的 INotifyPropertyChanged

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

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

它在 .NET(在 4 中测试)和 Silverlight(在 4 中测试)中编译。由于代码分为三个类,因此我已将代码发布到 gist 705450https://gist.github.com/705450 **

* )代码正常工作的一个原因是 ObservableCollection 还实现了 INotifyPropertyChanged,否则它将无法按预期工作,这是一个已知的警告

**)免费使用,根据 MIT 许可证发布

since I wasn't able to find a ready-to-use solution, I've done a custom implementation based on Pieters (and Marks) suggestions (thanks!).

Using the classes, you will be notified about any change in a deep object tree, this works for any INotifyPropertyChanged implementing Types and INotifyCollectionChanged* implementing collections (Obviously, I'm using the ObservableCollection for that).

I hope this turned out to be a quite clean and elegant solution, it's not fully tested though and there is room for enhancements. It's pretty easy to use, just create an instance of ChangeListener using it's static Create method and passing your INotifyPropertyChanged:

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

the PropertyChangedEventArgs provide a PropertyName which will be always the full "path" of your Objects. For example, if you change your Persons's "BestFriend" Name, the PropertyName will be "BestFriend.Name", if the BestFriend has a collection of Children and you change it's Age, the value will be "BestFriend.Children[].Age" and so on. Don't forget to Dispose when your object is destroyed, then it will (hopefully) completely unsubscribe from all event listeners.

It compiles in .NET (Tested in 4) and Silverlight (Tested in 4). Because the code in seperated in three classes, I've posted the code to gist 705450 where you can grab it all: https://gist.github.com/705450 **

*) One reason that the code is working is that the ObservableCollection also implements INotifyPropertyChanged, else it wouldn't work as desired, this is a known caveat

**) Use for free, released under MIT License

薄荷梦 2024-10-08 16:56:12

我认为您正在寻找类似于 WPF 绑定的东西。

INotifyPropertyChanged 的工作原理是 RaisePropertyChanged("BestFriend"); 必须 <仅当属性 BestFriend 更改时才会创建。当对象本身发生任何变化时就不会。

如何通过两步 INotifyPropertyChanged 事件处理程序来实现这一点。您的侦听器将注册 Person 的更改事件。当 BestFriend 被设置/更改时,您将注册 BestFriend Person 的更改事件。然后,您开始监听该对象的更改事件。

这正是 WPF 绑定实现此功能的方式。监听嵌套对象的变化是通过该系统完成的。

当您在 Person 中实现它时,这不起作用的原因是级别可能变得非常深,并且 BestFriend 的更改事件不再意味着任何内容(“什么改变了吗?”)。当你们有循环关系时,这个问题会变得更大,例如,你最好的朋友是你最好的朋友的母亲。然后,当其中一个属性发生更改时,就会出现堆栈溢出。

因此,解决这个问题的方法是创建一个可以用来构建侦听器的类。例如,您可以在 BestFriend.FirstName 上构建一个侦听器。然后,该类将在 Person 的更改事件上放置一个事件处理程序,并侦听 BestFriend 上的更改。然后,当情况发生变化时,它会在 BestFriend 上放置一个侦听器并侦听 FirstName 的变化。然后,当情况发生变化时,它会发送引发一个事件,然后您可以收听该事件。这基本上就是 WPF 绑定的工作原理。

请参阅http://msdn.microsoft.com/en-us/library/ms750413.aspx有关 WPF 绑定的更多信息。

I think what you're looking for is something like WPF binding.

How INotifyPropertyChanged works is that the RaisePropertyChanged("BestFriend"); must only be fored when the property BestFriend changes. Not when anything on the object itself changes.

How you would implement this is by a two step INotifyPropertyChanged event handler. Your listener would register on the changed event of the Person. When the BestFriend gets set/changed, you register on the changed event of the BestFriend Person. Then, you start listening on changed events of that object.

This is exactly how WPF binding implements this. The listening to changes of nested objects is done through that system.

The reason this is not going to work when you implement it in Person is that the levels can become very deep and the changed event of BestFriend does not mean anything anymore ("what has changed?"). This problem gets larger when you have circular relations where e.g. the best friend of your monther is the mother of your best fiend. Then, when one of the properties change, you get a stack overflow.

So, how you would solve this is to create a class with which you can build listeners. You would for example build a listener on BestFriend.FirstName. That class would then put an event handler on the changed event of Person and listen to changes on BestFriend. Then, when that changes, it puts a listener on BestFriend and listens for changes of FirstName. Then, when that changes, it sends raises an event and you can then listen to that. That's basically how WPF binding works.

See http://msdn.microsoft.com/en-us/library/ms750413.aspx for more information on WPF binding.

铁憨憨 2024-10-08 16:56:12

有趣的解决方案托马斯。

我找到了另一个解决方案。它称为传播器设计模式。您可以在网上找到更多信息(例如在 CodeProject 上:C# 中的传播器 - 观察者设计模式的替代方案)。

基本上,它是一种更新依赖网络中的对象的模式。当需要通过对象网络推送状态更改时,它非常有用。状态变化由通过传播器网络传播的对象本身来表示。通过将状态变化封装为对象,传播器变得松散耦合。

可重用传播器类的类图:

可重用传播器类的类图

阅读更多内容 CodeProject

Interesting solution Thomas.

I found another solution. It's called Propagator design pattern. You can find more on the web (e.g. on CodeProject: Propagator in C# - An Alternative to the Observer Design Pattern).

Basically, it's a pattern for updating objects in a dependency network. It is very useful when state changes need to be pushed through a network of objects. A state change is represented by an object itself which travels through the network of Propagators. By encapsulating the state change as an object, the Propagators become loosely coupled.

A class diagram of the re-usable Propagator classes:

A class diagram of the re-usable Propagator classes

Read more on CodeProject.

情绪失控 2024-10-08 16:56:12

我已经在网上搜索一天了,我从 Sacha Barber 找到了另一个不错的解决方案:

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

他在 Chained Property Observer 中创建了弱引用。如果您想了解实现此功能的另一种好方法,请查看这篇文章。

我还想提一下 Reactive Extensions @ 的一个很好的实现
http://www.rowanbeach.com/ rowan-beach-blog/a-system-reactive-property-change-observer/

此解决方案仅适用于一级观察者,不适用于完整的观察者链。

I have been Searching the Web for one day now and I found another nice solution from Sacha Barber:

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

He created weak references within a Chained Property Observer. Checkout the Article if you want to see another great way to implement this feature.

And I also want to mention a nice implementation with the Reactive Extensions @
http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/

This Solution work only for one Level of Observer, not a full Chain of Observers.

£冰雨忧蓝° 2024-10-08 16:56:12

我写了一个简单的助手来做到这一点。您只需在父视图模型中调用 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; });
    }

I wrote an easy helper to do this. You just call BubblePropertyChanged(x => x.BestFriend) in your parent view model. n.b. there is an assumption you have a method called NotifyPropertyChagned in your parent, but you can adapt that.

        /// <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; });
    }
朦胧时间 2024-10-08 16:56:12

在 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;
    }
}

Check-out my solution on CodeProject:
http://www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator
It does exactly what you need - helps to propagate (in elegant way) dependent properties when relevant dependencies in this or any nested view models change:

public decimal ExchTotalPrice
{
    get
    {
        RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice));
        RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate));
        return TotalPrice * ExchangeRate.Rate;
    }
}
浅笑依然 2024-10-08 16:56:12

请查看 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
}

Please take a look at EverCodo.ChangesMonitoring. This is a framework to handle PropertyChanged and CollectionChanged events on arbitrary hierarchy of nested objects and collections.

Create a monitor to handle all change events of the object tree:

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

Do arbitrary modifications on the object tree (all of them will be handled):

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>();

Handle all events in one place:

private void ChangesMonitor_Changed(object sender, MonitoredObjectChangedEventArgs args)
{
    // inspect args parameter for detailed information about the event
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文