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.
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
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.
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:
Read more on CodeProject.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With