This is an example code:
public class MyParent : INotifyPropertyChanged
{
List MyChildren;
public bool IsChanged
{
get
Something I do not see in your code sample provide is an actually reference of parent to child. It is not enough to simply have interface to communicate through, but you must also create the reference. Something like myChild.parent = this;
followed by the binding of the event handlers across the channel, in the "parent" property of the child object it would look like:
public INotifyPropertyChanged parent
{
get{return _parent;}
set
{
_parent = value;
this.PropertyChanged += _parent.RaiseChanged();
}
}
I don't have enough context to perfect this code for you but this should move you in the right direction.
Doing this kind of communication can be tricky, especially if you want to avoid memory leaks due to the event handlers that you hook up. There is also the case of handling items that are added / removed from the collection.
I've really enjoyed the power and simplicity of the Continuous LINQ project on codeplex. It has some very rich features for setting up "Reactive Objects", "Continuous Values", and "Continuous Collections". These let you define your criteria as a Linq expression and then let the CLINQ library keep the underlying values up to date in real time.
In your case, you could set up the parent with a ContinuousFirstOrDefault() linq query that watched for any child where "IsChanged == true". As soon as a child sets the value to true and raises PropertyChanged, the continuous value will detect the change and raise a corresponding PropertyChanged in the parent.
The benefits:
Here's what the code might look like:
public class MyParent : INotifyPropertyChanged
{
private ObservableCollection<MyChild> _MyChildren;
private ContinuousValue<MyChild> _ContinuousIsChanged = null;
public MyParent()
{
_MyChildren = new ObservableCollection<MyChild>();
// Creat the ContinuousFirstOrDefault to watch the MyChildren collection.
// This will monitor for newly added instances,
// as well as changes to the "IsChanged" property on
// instances already in the collection.
_ContinuousIsChanged = MyChildren.ContinuousFirstOrDefault(child => child.IsChanged);
_ContinuousIsChanged.PropertyChanged += (s, e) => RaiseChanged("IsChanged");
}
public ObservableCollection<MyChild> MyChildren
{
get { return _MyChildren; }
}
public bool IsChanged
{
get
{
// If there is at least one child that matches the
// above expression, then something has changed.
if (_ContinuousIsChanged.Value != null)
return true;
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyChild : INotifyPropertyChanged
{
private int _Value;
public int Value
{
get
{
return _Value;
}
set
{
if (_Value == value)
return;
_Value = value;
RaiseChanged("Value");
RaiseChanged("IsChanged");
}
}
private int _DefaultValue;
public int DefaultValue
{
get
{
return _DefaultValue;
}
set
{
if (_DefaultValue == value)
return;
_DefaultValue = value;
RaiseChanged("DefaultValue");
RaiseChanged("IsChanged");
}
}
public bool IsChanged
{
get
{
return (Value != DefaultValue);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
The above code sets up the ContinuousFirstOrDefault in the constructor so that it is always monitoring. However, in some cases you can optimize this by lazily instantiating the ContinuousFirstOrDefault only when the getter for "IsChanged" is called. That way you don't start monitoring for changes until you know that some other piece of code actually cares.
INotifyPropertyChanged
has already provided the mechanism for you: the PropertyChanged
event. Just have the parent add a handler to its children's PropertyChanged
, and then in that handler call RaiseChanged("IsChanged");
Also, you may want to put the INotifyPropertyChanged
implementation in a base class, and have your (what appear to be) ViewModels inherit from that. Not required for this option, of course, but it will make the code a little cleaner.
Update: In the parent object:
// This list tracks the handlers, so you can
// remove them if you're no longer interested in receiving notifications.
// It can be ommitted if you prefer.
List<EventHandler<PropertyChangedEventArgs>> changedHandlers =
new List<EventHandler<PropertyChangedEventArgs>>();
// Call this method to add children to the parent
public void AddChild(MyChild newChild)
{
// Omitted: error checking, and ensuring newChild isn't already in the list
this.MyChildren.Add(newChild);
EventHandler<PropertyChangedEventArgs> eh =
new EventHandler<PropertyChangedEventArgs>(ChildChanged);
newChild.PropertyChanged += eh;
this.changedHandlers.Add(eh);
}
public void ChildChanged(object sender, PropertyChangedEventArgs e)
{
MyChild child = sender as MyChild;
if (this.MyChildren.Contains(child))
{
RaiseChanged("IsChanged");
}
}
You don't actually have to add anything to the child class, since it is already raising the correct event when it changes.
You can simplify things for yourself by storing your children in an ItemObservableCollection<T>
, as discussed in this answer. That would allow you to do this:
private ItemObservableCollection<MyChild> children;
public MyParent()
{
this.children = new ItemObservableCollection<MyChild>();
this.children.ItemPropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
if (string.Equals("IsChanged", e.PropertyName, StringComparison.Ordinal))
{
this.RaisePropertyChanged("IsChanged");
}
};
}
I'm not quite familiar with C++ code, but this sounds much like the "observer pattern" (Wikipedia, another good page for OO design. In simple words, any instance should raise an event whenever it has changed, and any other instance which holds a reference to it may catch this event and react appropriately.