Tronald Tronald - 3 months ago 10
C# Question

Bind to total of an items field in an ObservableCollection and update when values change

I have an ObservableCollection that populates a datagrid in WPF. I need to bind to the total of the "Hours" column, and have that total update when a value in the "Hours" column is changed. I can achieve this by listening to the "LostFocus" event and running a function, but would like to try my hand at binding.

The issue I am running into, is the NotifyPropertyChanged event will not fire when an items property in the collection is changed.

The sortie class NotifyPropertyChanged will fire, but the collection doesn't interpret that as its own property changing. How can I listen to the sortie PropertyChanged from the collection in the missions class?

My Models

public class Mission : INotifyPropertyChanged
{
private ObservableCollection<Sortie> sorties;
public ObservableCollection<Sortie> Sorties
{
get { return this.sorties; }
set
{

if (this.sorties != value)
{
this.sorties = value;
this.NotifyPropertyChanged("Sorties");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}

public class Sortie : INotifyPropertyChanged
{
private double hours;
public double Hours
{
get {return this.hours;}
set
{
if (this.hours != value)
{
this.hours = value;
this.NotifyPropertyChanged("Hours");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}


I didn't bother posting my XAML or View Model, as I am confident I can solve the issue once I learn how to trigger a PropertyChanged event for the collection and I wanted to spare you having to read through massive amounts of code. If you believe it is needed however, let me know.

Answer

Write a readonly property in the parent viewmodel that calculates the value.

public double SortieHours => Sorties.Sum(x => x.Hours);

Parent viewmodel handles PropertyChanged on each item in Sorties, and CollectionChanged on Sorties. In CollectionChanged on Sorties, you have to add/remove PropertyChanged handlers from Sortie instances as they're added and removed. When you get a new Sorties collection (you might want to make that setter private for this reason), you need to toss out all the old handlers and add new ones.

Now, whenever a Sortie is added or removed, or its Hours changes, or somebody hands you a new Sorties collection, raise PropertyChanged:

OnPropertyChanged(nameof(SortieHours));

And bind that property to whatever you like in the XAML.

This looks horrible (because it is), but what else are you going to do?

A lot of people would advise you to give Sortie an HoursChanged event. PropertyChanged is annoying for this type of case because it can get raised for multiple different properties and you have to check which one. And it's a magic string thing.

Above is C#6. For C#5,

public double SortieHours { get { return Sorties.Sum(x => x.Hours); } }

OnPropertyChanged("SortieHours");
Comments