2

We have a WPF application, let’s say I have a Department object, in it I have an ObservableCollection of Courses, inside each Course I have an ObservableCollection of Teachers, and inside each Teacher I have an ObservableCollection of Student:

Department.cs ---- ObservableCollection Courses

Course.cs ---- ObservableCollection Teachers

Teacher.cs ---- ObservableCollection Students

Student.cs ---- Name, Age, etc

These 4 classes all implemented INotifyPropertyChanged, they are working fine by themselves, what I want to achieve is whenever anything inside the Department object graph is changed, whether it was a student’s age get changed, or a new course added or a teacher removed, anything at all, I want to know about it, ideally via an event like DepartmentChanged (and ideally this only raise once), so I can call some other long running action.

I have a hard time coming up with a clean way to do it. I understand that INotifyPropertyChanged only track property change of a class and ObservationCollection has no idea if one of its items has changed something.

So far I tried nested loop like (pseudo code):

foreach (var course in d.Courses){
    foreach (var teacher in course.Teachers){
        foreach (var student in teacher.Students){
            ((INotifyPropertyChanged)student).PropertyChanged += ...
        }
        teacher.Students.CollectionChanged += ...
    }
}

But as can imagine, this created lots of PropertyChanged events since every property subscribe to that, and the action I wanted to do in Department ended up being executed many many times, which is bad.

I then tried using a timer (each of our class has an IsDirty() to determine if the object has changed) with interval set to 500:

if(department != null && department.IsDirty()){
    //call some long running Action here
}

While this seems to work, somehow I feel this approach is wrong, I'd really like to avoid timer if possible, I would love to hear other suggestions and ideas!

Thanks,

wliao
  • 159
  • 3
  • 11
  • Any change and run the same long running long procedure. What is the nature of this long procedure that you cannot be a little more specific and deal with property change in the property set? – paparazzo Apr 05 '12 at 15:41
  • Take a look at this implementation of an [ObservableCollection that also monitors changes on the elements in collection](http://stackoverflow.com/a/269113/620360). – LPL Apr 05 '12 at 15:24
  • @LPL, thanks, that post seems interesting, I'll definitely study it more. – wliao Apr 05 '12 at 18:10
  • @Blam, I mentioned "long running action" just to illustration a point, the point is I don't want to call it more than once, whatever the code may be. – wliao Apr 05 '12 at 18:11
  • OK, what is the nature of the action (long or short) that you need to run the same exact same action regardless of what property changed or was added. – paparazzo Apr 05 '12 at 18:30
  • @Blam, I don't quite follow how that'd be relevant. But I have some calculations that need to be performed, and that the result of the calculation is affected by most of the properties. Is that what you meant? thanks. – wliao Apr 05 '12 at 18:46
  • You state the approach feels wrong. It is hard to evaluate an approach if the nature of the calculation is not know. If it is a push number then how do you push it out. If it is pull calculation then why are you not calculating on demand. There are only 4 property changed and 3 collection changed events - that is 7 calls - you already have more then 7 lines trying to wire up events and it is not working. Why does this have to be single subscribed event? – paparazzo Apr 05 '12 at 19:13

2 Answers2

1

This is just a quick idea, without a timer, can you implement IsDirty event on the Course, & Teacher classes?

For example, Teacher, managers on PropertyChanged internally (subscribes/unsubscribes properly when Student collection changes). When any of its students have a valid change, all it has to do is raise IsDirty event, which all have student obj as a param.

Course will listen and capture teacher.IsDirty event, and will raise its own course.IsDirty where CourseEventArgs = new CourseEventArgs will have a Course, Teacher with Student that has dirty fields.

In the Department class you listen to Course IsDirty event and parse the args...

This idea, seems to do the same thing that you have, but not really, because it handles any PropertyChanged only on the lowest level then takes use of your own events to bubble necessary info to the top. Which separates top classes from Student's PropertyChanged behavioral implementations as well as extensive chatter.. Also if on the student level, you make changes (through UI, and post them, click save or whatever) then you can control when IsDirty is being raised, maybe only once.

denis morozov
  • 6,236
  • 3
  • 30
  • 45
  • thank you for the suggestion, if I understand correctly this would involve modifying all the class properties to specifically raise the IsDirtyEvent when the value is changed, beside raising the PropertyChanged event, is that correct? – wliao Apr 05 '12 at 18:09
  • Yes, and in order for you to have custom args you'd have to create your own (public class EventArgs : EventArgs) – denis morozov Apr 05 '12 at 18:28
  • Our team is considering this approach for now, I'll mark this as accepted unless something else surfaced. Thanks everyone for the suggestions. – wliao Apr 10 '12 at 17:36
1

I think you'll still have to use an IsDrity bit and INotifyPropertyChanged at the individual object level, but why don't you extend ObservableCollection<T> and override a few basic methods to handle registering for the object property changes? So for example, create something like:

public class ChangeTrackerCollection<T> : ObservableCollection<T>
{
    protected override void ClearItems()
    {
        foreach (var item in this)
            UnregisterItemEvents(item);

        base.ClearItems();
    }

    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);

        RegisterItemEvents(item);
    }

    protected override void RemoveItem(int index)
    {
        UnregisterItemEvents(this[index]);

        base.RemoveItem(index);
    }

    private void RegisterItemEvents(T item)
    {
        item.PropertyChanged += this.OnItemPropertyChanged;
    }

    private void UnregisterItemEvents(T item)
    {
        item.PropertyChanged -= this.OnItemPropertyChanged;
    }

    private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //TODO: raise event to parent object to notify that there was a change...
    }
}
John Russell
  • 2,177
  • 4
  • 26
  • 47