views:

692

answers:

2

I have a treeview which binds to lots of nested ObservableCollections. Each level of the treeview shows an aggregated sum of all the hours in child items. For example:

Department 1, 10hrs
  ├ Team 10, 5hrs  
  │  ├ Mark, 3hrs
  │  └ Anthony, 2hrs   
  └ Team 11, 5hrs
     ├ Jason, 2hrs
     ├ Gary, 2hrs
     └ Hadley, 1hrs  
Department 2, 4hrs   
  ├ Team 20, 3hrs  
  │  ├ Tabitha, 0.5hrs
  │  ├ Linsey, 0.5hrs
  │  └ Guy, 2hrs
  └ Team 11, 1hr
     └ "Hadley, 1hr"  

When I modify my Individual.Hours in my ViewModel class i want to update the hours values in both my team and departments too.

I'm already using NotificationProperties for all my Hours properties, and ObservableCollections for Teams in Departments and Individuals in Teams.

Thanks,
Mark

+1  A: 

I am affraid you will have to explicity call the notification on the parent ObservableCollection container for each individual's parent ('Team').

Then from the individual parent, set notification event for grand parent ('Department').

Team.OnPropertyChanged("Individuals")

Shimmy
Thanks, I thought this was the case.
Mark Cooper
+4  A: 

Each department's hours depends on the aggregate of its team's hours. Each team's hours depends on the aggregate of its individual's hours. Thus, each Team should listen for changes to any of its individual's Hours property. When detected, it should raise OnPropertyChanged for its own Hours property. Similarly, each Department should listen for changes to any of its team's Hours property. When detected, it should raise OnPropertyChanged for its own Hours property.

The end result is that changing any individual's (or team's) hours is reflected in the parent.

Pseduo code that can be greatly improved with refactoring but gives the essence of the answer:

public class Individual : ViewModel
{
    public int Hours
    {
        // standard get / set with property change notification
    }

}

public class Team : ViewModel
{
    public Team()
    {
        this.individuals = new IndividualCollection(this);
    }

    public ICollection<Individual> Individuals
    {
        get { return this.individuals; }
    }

    public int Hours
    {
        get
        {
            // return sum of individual's hours (can cache for perf reasons)
        }
    }

    // custom collection isn't strictly required, but makes the code more readable
    private sealed class IndividualCollection : ObservableCollection<Individual>
    {
        private readonly Team team;

        public IndividualCollection(Team team)
        {
            this.team = team;
        }

        public override Add(Individual individual)
        {
            individual.PropertyChanged += IndividualPropertyChanged;
        }

        public override Remove(...)
        {
            individual.PropertyChanged -= IndividualPropertyChanged;
        }

        private void IndividualPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Hours")
            {
                team.OnPropertyChanged("Hours");
            }
        }
    }
}

public class Department : ViewModel
{
    public Department()
    {
        this.teams = new TeamCollection();
    }

    public ICollection<Team> Teams
    {
        get { return this.teams; }
    }

    public int Hours
    {
        get
        {
            // return sum of team's hours (can cache for perf reasons)
        }
    }

    // TeamCollection very similar to IndividualCollection (think generics!)
}

Note that if performance becomes an issue you can have the collection itself maintain the hour total. That way, it can do a simple addition whenever a child's Hours property changes, because it is told the old value and the new value. Thus, it knows the difference to apply to the aggregate.

HTH,
Kent

Kent Boogaart