views:

163

answers:

4

Is there a way to watch an object graph for changes on any object, and do something based on that change?

Lets say I have the following:

public class Main:INotifyPropertyChanged
{
    public ObservableCollection<Foo> FooItems { get; }
    public ObservableCollection<Bar> BarItems { get; }
}

public class Foo:INotifyPropertyChanged

public class Bar:INotifyPropertyChanged
{
    public ObservableCollection<Other> OtherItems { get; }
}

public class Other:INotifyPropertyChanged

What would be the best way to implement some sort of change notification system across all objects? For example an autosave, where any change would trigger the system to serialize the Main class.

Should I have glue code in the Main class watching the BarItems for changes, hooking up to their PropertyChanged? This seems a bit messy, and error prone to me. Is there a better way?

A: 

I just read an interesting blog post on that issue at http://www.lennybacon.com/ReBlinderFleckChangeTracking.aspx

The post is in German, but as it's mostly code, it should be OK.

Hope this helps!

Andre Kraemer
+3  A: 

Rather than objects raising their own property changed events, perhaps they could raise a shared event instead. For example:

public class SharedChangeNotifier
{
    public static event EventHandler<DataChangedEventArgs> SharedChangeEvent;

    protected void RaiseChangeEvent()
    {
        if (SharedChangeNotifier.SharedChangeEvent != null)
        {
            SharedChangeNotifier.SharedChangeEvent(
                this, new DataChangedEventArgs());
        }
    }
}

public class Foo : SharedChangeNotifier
{
    public int MyProperty
    {
        get { ... }
        set
        {
            ...
            RaiseChangeEvent();
        }
    }
}

You could then attach an event handler to the static SharedChangeNotifier's SharedChangeEvent to be notified whenever any object deriving from SharedChangeNotifier is changed, like this:

SharedChangeNotifier.SharedChangeEvent += (sender, args) => {
    DoWhatever();
};
Jacob
I could do this, but I'm also using the INotifyPropertyChanged interface for data binding, so it would be nice to use what's already there. I'll keep this in mind though.
Cameron MacFarland
A: 

The way I have done it in the past was to create a separate ChangeTracker class with a method to Register objects into it. Inside that method, use reflection to explore the registered object, and hook into events on each of its properties that implements INotifyPropertyChanged.

You can then add methods to the ChangeTracker to interrogate the state, e.g. IsDirty(), or even implement INotifyPropertyChanged on the ChangeTracker.

(Be sure to implement and use IDisposable on the ChangeTracker, and drop all the event handlers at that time).

Steve Campbell
A: 

You could have the same handler for all items that implement INotifyPropertyChanged events:

foreach (INotifyPropertyChanged obj in FooItems)
    obj.PropertyChanged+= this.modified;

// likewise for bar items, and when items are added

private void modified(object sender, EventArgs args)
{
    this.Save();
}

edit> To do the same when an item is added:

private void addToList<T>(ref List<T> l, T item) where T : INotifyPropertyChanged
{
    item.PropertyChanged += this.modified;
    l.Add(item);
}

call it using:

Foo item = new Foo();
List<Foo> fooItems = new List<Foo>();

addToList<Foo>(ref fooItems, item);
SnOrfus
What about when an item is added or removed from FooItems?
Cameron MacFarland