Monitoring an object for changes  
Author Message
Sweeps78





PostPosted: Visual C# General, Monitoring an object for changes Top

Hi,

I have an object which contains many Properties. Actually, this object is only one type of object inside of a hierarchal based object model using the Collections.ObjectModel.KeyedCollection.

To give you an idea as to what I am trying to do, let me explain some of the objects and the properties that I need notification of.

"Rod" class has a .Length property.
"Part" has a .Shape, .Pin, and .Socket property.
"Wheel" has a .Diameter and .Radius property.
"NonStationary" has a .CurrentValue property.

That's just a small example, just to get to my question. I was wondering what is the standard practice of notifying when property values have changed. The trick is that I am not concerned about single individual object property changes, only Types within the object model, for example, whenever any .Length changes of any Rod object within the hierarchal object model. The only thing I could think of was creating Static Events, which would work in my case, as (using the same example) then I could issue something like:

Rod.OnRodLengthChange += new EventHandler<EventArgs>(Rod_OnRodLengthChange);

However, I am guessing that there is a better way that I am just not aware of. Any suggestions I would appreciate tremendously!



Visual C#3  
 
 
TaylorMichaelL





PostPosted: Visual C# General, Monitoring an object for changes Top

One way is for your object to implement the INotifyPropertyChanged interface. This interface has a single member which is the PropertyChanged event. The event is raised whenever a property value changes. Clients are required to handle the event and react based upon the property that changed. In base classes this is normally implemented as a protected method that raises the event, if needed. Derived classes call the method with the property name when setting a property value and the value does not match the current value. This is the preferred route for business objects and is implicitly supported by the designer, binding list and some other controls.

Another way, which adds way more events but is easier to code against, is to create a separate event for each property of interest. In this case you should set the event type as EventHandler. Clients can retrieve the current value by going back to the parent object. Controls in .NET generally follow this approach.

Michael Taylor - 12/5/06


 
 
James Kovacs





PostPosted: Visual C# General, Monitoring an object for changes Top

Although static events would work, it doesn't leave you a lot of flexibility in your object model. If you need to create two independent composite objects, you wouldn't be able to separately subscribe to change in either. I would recommend taking a look at the Observer pattern (http://www.dofactory.com/Patterns/PatternObserver.aspx), which is what events are based on. Typically the Subject (i.e. object being observed) is the object upon which property changes happen. As an alternate solution, create a PropertyChangedAlerter to which client objects can subscribe. Pass an instance of PropertyChangedAlerter to every Rod object. (You may want to use an Abstract Factory (http://www.dofactory.com/Patterns/PatternAbstract.aspx) to localize insertion of the PropertyChangedAlerter to all objects.) When a property changes, you could do something like this:

// WARNING - I haven't tried compiling this. For demonstration purposes only.
public class Rod {
private PropertyChangedAlerter m_alerter;

internal Rod(PropertyChangedAlerter alerter) {
m_alerter = alerter;
}

public int Length {
get { return m_length; }
set {
// Guard clause to prevent spurious notifications
if(m_length == value) {
return;
}
m_length = value;
m_alerter.Notify(this); // Pass yourself so the alerter can tell the subscribers which
// object changed. You may also want to pass an EventArgs
// if there is useful info to send. Otherwise just let the alerter
// use an empty one.
}
}

I hope that gives you some ideas.


 
 
Sweeps78





PostPosted: Visual C# General, Monitoring an object for changes Top

Will I still be able to use these options if a property is of a reference type For example, say I have Rod object, which has a Pin object Property. The Pin object contains the following properties:

Xaxis
Yaxis
Zaxis
Xrotation
Yrotation
Zrotation

Will I be able to monitor when any of these properties change within the Pin object Property of the Rod object

EDIT:

After reviewing these options, I don't see an option that will help in telling me when a reference type property has changed as discussed above. Is there anything out there that notifies me in when this is the case


 
 
TaylorMichaelL





PostPosted: Visual C# General, Monitoring an object for changes Top

Each type has to implement it themselves so your Rod class would also have to do the same thing. You could also expose the child property changes in the parent class if desired but you'll have to trap the change in the parent class and send the notification.

Michael Taylor - 12/5/06


 
 
James Kovacs





PostPosted: Visual C# General, Monitoring an object for changes Top

You could do this by using the same PropertyChangedAlerter object for the child objects as for the parent. Another option would be for the parent to register to receive change events from the child and then propagate the notification via calling PropertyChangedAlerter.Notify(this).


 
 
Sweeps78





PostPosted: Visual C# General, Monitoring an object for changes Top

Maybe I'm misunderstanding you guys, but I am not sure if you guys are understanding what I am referring to in my last post, so let me try this (I'm leaving out the rotation properties for simplicity):

public class Rod
{
public Rod(PositionOrientation pin) { Pin = pin; }
private _pin;

public PositionOrientation Pin
{
get { return _pin; }
set { _pin = value; }
}
}


public class PositionOrientation
{
private double _xAxis;
private double _yAxis;
private double _zAxis;

public PositionOrientation(double xAxis, double yAxis, double zAxis)
{
Xaxis = xAxis;
Yaxis = yAxis;
Zaxis = zAxis;
}

public double Xaxis
{
get { return _xAxis; }
set { _xAxis = value; }
}

public double Yaxis
{
get { return _yAxis; }
set { _yAxis = value; }
}

public double Zaxis
{
get { return _zAxis; }
set { _zAxis = value; }
}
}

public partial class Form1 : Form
{
public Rod r = new Rod(new PositionOrientation(0.5, 0.3, 5));

private void Form1_Load(object sender, EventArgs e)
{
r.Pin.Xaxis = .3;
r.Pin.Yaxis = 0.0;
r.Pin.Zaxis = 0.5;
}
}

So, as you can see the Pin Property isn't a child object, it is a reference Property. My question is, in the above code in Form1_Load, how do I detect that the Rod object's Pin Property property's (is that the correct way of saying that ) have changed Obviously, I would be accessing the get {} of the Pin Property each time I adjust Xaxis, Yaxis, and Zaxis because the only time I would access the set {} would be when I:

r.Pin = new PositionOrientation(0.0, 2.5, 5.0);

The problem is that I can't just put an event inside the get, because this would constantly fire in situations when the Rod object is displayed in a property grid and even so, there is no way in this case to detect a change. Also, as far as I know, I can't have a PropertyEventChanged that would fire inside the PositionOrientation class each time any of the Axis's change, because there's no way that I'm aware of that would say "This PositionOrientation object belongs to this Pin of this Rod" which is a necessity of my system.

Hopefully that explained things a bit. BTW, I really appreciate all of the help you guys are giving me:) It has "expanded my horizons"


 
 
TaylorMichaelL





PostPosted: Visual C# General, Monitoring an object for changes Top

For the INotifyPropertyChanged approach you'd do this:

public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged ( string name )
{
PropertyChangedEventHandler hdlr = PropertyChanged;
if (hdlr != null)
hdlr(this, new PropertyChangedEventArgs(name));
}
}

public class Rod : NotifyPropertyChanged
{

public Rod(PositionOrientation pin) { Pin = pin; }
private _pin;

public PositionOrientation Pin
{
get { return _pin; }
set
{
if (_pin != value)
{
_pin = value;
OnPropertyChanged("Pin");
};
}
}
}


public class PositionOrientation : NotifyPropertyChanged
{
private double _xAxis;
private double _yAxis;
private double _zAxis;

public PositionOrientation(double xAxis, double yAxis, double zAxis)
{
Xaxis = xAxis;
Yaxis = yAxis;
Zaxis = zAxis;
}

public double Xaxis
{
get { return _xAxis; }
set
{
if (_xAxis != value)
{
_xAxis = value;
OnPropertyChanged("Xaxis");
};
}
}

public double Yaxis
{
get { return _yAxis; }
set
{
if (_yAxis!= value)
{
_yAxis= value;
OnPropertyChanged("YAxis");
};
} }

public double Zaxis
{
get { return _zAxis; }
set
{
if (_zAxis != value)
{
_zAxis= value;
OnPropertyChanged("Zaxis");
};
} }
}

public partial class Form1 : Form
{
public Rod r = new Rod(new PositionOrientation(0.5, 0.3, 5));

private void Form1_Load(object sender, EventArgs e)
{
r.Pin.Xaxis = .3; //Raises an event for anyone listening to Pin's PropertyChanged event
r.Pin.Yaxis = 0.0; //Ditto
r.Pin.Zaxis = 0.5; //Ditto
}
}


You can also hook into the child Pin's PropertyChanged event in Rod and raise the PropertyChanged event at the Rod level to remove the requirement from clients for having to track the child object's as well:

public class Rod : NotifyPropertyChanged
{

public Rod(PositionOrientation pin) { Pin = pin; }
private _pin;

public PositionOrientation Pin
{
get { return _pin; }
set
{
if (_pin != value)
{
if (_pin != null)
_pin.PropertyChanged -= OnPinPropertyChanged;

_pin = value;
if (_pin != null)
_pin.PropertyChanged += OnPinPropertyChanged;

OnPropertyChanged("Pin");
};
}
}

private void OnPinPropertyChanged ( object sender, PropertyChangedEventArgs e )
{
OnPropertyChanged("Pin." + e.PropertyName);
}
}

Michael Taylor - 12/5/06


 
 
James Kovacs





PostPosted: Visual C# General, Monitoring an object for changes Top

Michael's implementation is a good one. If you would rather use the PropertyChangedAlerter, you would do something like this:
public class Rod {
private PropertyChangedAlerter m_alerter;
private List<Pin> m_pins;

internal Rod(PropertyChangedAlerter alerter) {
m_alerter = alerter;
m_pins = new List<Pin>();
}

public void AddPin() {
Pin p = new Pin(m_alerter);
m_pins.Add(p);
}

public int Length {
get { return m_length; }
set {
// Guard clause to prevent spurious notifications
if(m_length == value) {
return;
}
m_length = value;
m_alerter.Notify(this); // Pass yourself so the alerter can tell the subscribers which
// object changed. You may also want to pass an EventArgs
// if there is useful info to send. Otherwise just let the alerter
// use an empty one.
}
}



The implementation for Pin would have properties similar to Rod's Length property. PropertyChangedAlerter would have a PropertyChanged event that clients would register for. The advantage of this technique is that it for large graphs of Rod/Pin/etc. objects, the number of delegates would be kept to a minimum.


 
 
Sweeps78





PostPosted: Visual C# General, Monitoring an object for changes Top

Unfortunately, neither one of these techniques will work for what I am referring to, as in my previous post, "because there's no way that I'm aware of that would say This PositionOrientation object belongs to this Pin of this Rod."

This may be a bit confusing, so let me explain a bit. I'll use snippets of Michael's example because it is the closest to what I am used to dealing with:

public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged ( string name )
{
PropertyChangedEventHandler hdlr = PropertyChanged;
if (hdlr != null)
hdlr(this, new PropertyChangedEventArgs(name));
}
}


public double Yaxis
{
get { return _yAxis; }
set
{
if (_yAxis!= value)
{
_yAxis= value;
OnPropertyChanged("YAxis");
};
} }


By calling the OnPropertyChanged method here (which would in turn fires the event), all that the Object sender parameter (on the user side) would return is a PositionOrientation object. This sender Object parameter would give me no information that this is a Pin's Property contained inside a Rod object. I am using PositionOrientation for more things then just a Pin property of a Rod in my object model, so assumptions won't work:(

So, this leads me to my second concern:

public PositionOrientation Pin
{
get { return _pin; }
set
{
if (_pin != value)
{
_pin = value;
OnPropertyChanged("Pin");
};
}
}

The only time the OnPropertyChanged method here would get called is when the user decides to create a new Pin object:

Rod r = new Rod(new PositionOrientation(0.5, 5.0, 6.8);
r.Pin = new PositionOrientation(8.8, 3.4, 7.7); <-- This is what would cause the call to OnPropertyChange

Not when:
r.Pin.Xaxis = 0.3;

Doesn't access 'set' only 'get'; there would be no call to OnPropertChange

There are many different Rod objects all throughout my object model, each with their own unique Pin's. I need to know when the Xaxis, Yaxis, or Zaxis Properties change of every Rod object's Pin Property. I also need to know just what Rod object that Pin property belongs to.




 
 
TaylorMichaelL





PostPosted: Visual C# General, Monitoring an object for changes Top

If I understand you correctly all you really want to know is when some subproperty of a property changes. The code that I posted will do exactly that. The problem is that you would have to hook the PropertyChanged for both the Rod and Pin objects which might or might not be what you want. The second portion of the code that I gave you eliminates the need for hooking both objects. Whenever a property on the Pin object (which is nested inside the Rod object) changes the Pin object raises the PropertyChanged event. The parent Rod object handles this event (as could anyone else) and raises its own PropertyChanged event. In this case however the sender would be the Rod but the property name would be Pin.property to distinguish between the scopes. If you had another property called Pin2 on Rod and it raised the event then the property name would be Pin2.property. Note also that since Pin is a reference type if anyone changes a property on the object whether they go through Rod or not you'll still get the notification which might be confusing.

Rod rod = new Rod(...);
PositionOrientation pin1 = new PositionOrientation(...);
rod.Pin = pin1;
pin1.XAxis = ...; //Raises the PropertyChanged event on Rod even though you changed it outside the Rod object

However if all you are trying to do is get the PropertyGrid to display your property values (I believe you mentioned this in an earlier post) then you really don't have to do anything because the PG will handle this automatically. The few cases it doesn't handle you just have to attach attributes to provide notifications.

Michael Taylor - 12/6/06


 
 
Sweeps78





PostPosted: Visual C# General, Monitoring an object for changes Top

I tried your first example from your post that starts with, "For the INotifyPropertyChanged approach you'd do this:" The one that demonstrates the use of an abstract NotifyPropertyChanged class inheriting from the INotifyPropertyChanged Inteface.
What I thought would happen, happened. There is no way of telling who the PositionOrientation object (the parent) belongs to when I adjust the Pin's XAxis, YAxis, or ZAxis.

Rod rod = new Rod(...);
rod.Pin.XAxis = 34.5;
When the PropertyChanged event fires, the sender parameter is a PositionOrientation object and gives no indication that it was a Pin Property, nor does it give any information to the Rod object that the Pin belonged to.

Additionally, I as you had mentioned, I have to hook two PropertyChanged events: One for the Pin, and one for the Rod:

Rod rod = new Rod(...);
rod.PropertyChanged += new PropertyChangedEventHandler(...);
rod.Pin.PropertyChanged += new PropertyChangedEventHandler(...);

the first hook fires when I create a new PositionOrientation object and pass it to the Pin:

rod.Pin = new PositionOrientation(...);

the second hook fires when I change any of the axis's properties of the PositionOrientation:

rod.Pin.XAxis = 34.0;

As I stated above, the event tied to modifying the XAxis in this fashion only returns a PositionOrientation in the sender object, nothing else.


Also, hooking two events is what I would like to avoid if possible. I need this event to fire for any Rod's Pin inside my object model. I can't set this up in a way that would require the user to hook an event every time they create and add a Rod to the object model. There needs to be one event for all the Rod objects. That is why I originally suggested static events.

EDIT:
Also, the second example that you provided won't work in the way that you mentioned, because anytime the rod.Pin.properties are accessed, the get{} accessor of the Pin is the area that is accessed, not the set{} accessor. The set{} accessor is only accessed when you create a new PositionOrientation object and assign it to rod.Pin.


 
 
TaylorMichaelL





PostPosted: Visual C# General, Monitoring an object for changes Top

Yes the first example requires two hooks. That is why I provided an alternative method in the same post that gives you exactly what you wanted. To avoid confusion here is the full code that works the way you want:

public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged ( string name )
{
PropertyChangedEventHandler hdlr = PropertyChanged;
if (hdlr != null)
hdlr(this, new PropertyChangedEventArgs(name));
}
}

public class Rod : NotifyPropertyChanged
{

public Rod(PositionOrientation pin) { Pin = pin; }
private _pin;

public PositionOrientation Pin
{
get { return _pin; }
set
{
if (_pin != value)
{
if (_pin != null)
_pin.PropertyChanged -= OnPinPropertyChanged;

_pin = value;
if (_pin != null)
_pin.PropertyChanged += OnPinPropertyChanged;

OnPropertyChanged("Pin");
};
}
}

private void OnPinPropertyChanged ( object sender, PropertyChangedEventArgs e )
{
OnPropertyChanged("Pin." + e.PropertyName);
}
}


public class PositionOrientation : NotifyPropertyChanged
{
private double _xAxis;
private double _yAxis;
private double _zAxis;

public PositionOrientation(double xAxis, double yAxis, double zAxis)
{
Xaxis = xAxis;
Yaxis = yAxis;
Zaxis = zAxis;
}

public double Xaxis
{
get { return _xAxis; }
set
{
if (_xAxis != value)
{
_xAxis = value;
OnPropertyChanged("Xaxis");
};
}
}

public double Yaxis
{
get { return _yAxis; }
set
{
if (_yAxis!= value)
{
_yAxis= value;
OnPropertyChanged("YAxis");
};
} }

public double Zaxis
{
get { return _zAxis; }
set
{
if (_zAxis != value)
{
_zAxis= value;
OnPropertyChanged("Zaxis");
};
}
}
}

public partial class Form1 : Form
{
public Rod r = new Rod(new PositionOrientation(0.5, 0.3, 5));

private void Form1_Load(object sender, EventArgs e)
{
PositionOrientation pin = r.Pin;

r.Pin.Xaxis = .3; //Raises the Rod.PropertyChanged event with sender = r and PropertyName = Pin.Xaxis; also raises Pin.PropertyChanged with sender = Pin and PropertyName = Xaxis
r.Pin.Yaxis = 0.0; //Raises the Rod.PropertyChanged event with sender = r and PropertyName = Pin.Yaxis; also raises Pin.PropertyChanged with sender = Pin and PropertyName = Yaxis
r.Pin.Zaxis = 0.5; //Raises the Rod.PropertyChanged event with sender = r and PropertyName = Pin.Zaxis; also raises Pin.PropertyChanged with sender = Pin and PropertyName = Zaxis

pin.Xaxis = 4; //Raises the Rod.PropertyChanged event with sender = r and PropertyName = Pin.Xaxis; also raises Pin.PropertyChanged with sender = Pin and PropertyName = Xaxis
}
}

Isn't this what you want

Michael Taylor - 12/6/06


 
 
Sweeps78





PostPosted: Visual C# General, Monitoring an object for changes Top

WOW! Behold, the sheer power of virtual methods!!

I totally didn't see the solution in your example here at first, so I created a new Windows application based off of it. I wanted to smack myself once I saw that it worked, haha! Thanks for "spelling it out" for me

This is about 90% what I was looking for. The last part is setting this up in a way that allows the user to hook one OnPropertyChanged event for all Rod objects. The way it is now, is the user would have to hook OnPropertyChanged events for all Rod objects in the object model. The only solution I could think of was what I had originally mentioned, static events...

Thank you for all of your help, and patience. Every once in a while it takes a sledgehammer to pound an idea in my head, but once it's in there, it never leaves, haha!



 
 
Sweeps78





PostPosted: Visual C# General, Monitoring an object for changes Top

Hey James!

Just wanted to let you know that I finally understand your example. At first it was pretty far over my head, but once I created a project based on the link you provided, it all made sense! You're right, this is a very flexible option, thanks!

I think I'm going to have to take knowledge I learned from both of you and create
my solution.