views:

271

answers:

1

I have a master-detail relationship in some custom entities. Say I have the following structure:

class Master : INotifyPropertyChanged
{
    public int Id { get; set; } // + property changed implementation 
    public string Name { get; set; } // + property changed implementation

    public ObservableCollection<Detail> Details { get; }
}

class Detail : INotifyPropertyChanged
{
     public int Id { get; private set; }
     public string Description { get; set; }
     public double Value { get; set; } // + property changed implementation
}

My goal is to have a ListView, using a GridView, showing a list of Master objects. When I select a specific Master, I'll have a separate ListView for the details, allowing editing. Basically, a fairly standard Master-Detail view.

However, I also want the GridView for the Master to show the Sum of all of that master's Detail elements, ie: Details.Select(d => d.Value).Sum();

This is fairly easy to display using a custom IValueConverter. I can convert from the details collection directly to a double displaying sum, and bind a TextBlock's Text to the Details OneWay, via the IValueConverter. This will work, and show the correct values when I open the window.

However, if I change one of the detail members, this will not update (even though detail implements INotifyPropertyChanged), since the collection itself is still the same (the ObservableCollection reference hasn't changed).

I want to have an aggregated value in a master list, showing the sum (or average/count/etc) within the detail list, and have this stay up to date when the user changes properties in details. How can I go about implementing this?


Edit:

Ideally, I would prefer if there is a means of accomplishing this that doesn't involve changing the Master class directly. The application in question is using the MVVM pattern, and I'd really prefer to not change my Model classes in order to implement a specific View. Is there a way to do this without introducing custom logic into the model?

+3  A: 

I was considering possibilities with the UI where you'd make the binding explicit and perform binding/updates from a command... but it seems that the easiest way to do it would be to extend the ObservableCollection to add/remove listeners to each Detail instance as its added/removed, then just fire CollectionChanged when any of them change. Call it DeeplyObservableCollection<T>.

class Master : INotifyPropertyChanged
{
    public int Id { get; set; } // + property changed implementation 
    public string Name { get; set; } // + property changed implementation
    public double Sum {get {return Details.Sum(x=>x.Value);}}

    public DeeplyObservableCollection<Detail> Details { get; }

    // hooked up in the constructor
    void OnDOCChanged(object sender, CollectionChangedEventArgs e) 
    { OnPropertyChanged("Sum"); }
}

Worst case you'd have to wrap an ObservableCollection in another type if you can't properly override all the methods you need...

Will
+1, since this does work. I had thought about this, but I'd really like to do this without changing the Master class, in an ideal world. Implementing this means changing the Model to allow the View to work, which I just don't like...
Reed Copsey
Well, not really... It isn't much different than writing the ObservableCollection in the first place.
Will
Except that now my entity has a "Sum" property, that's purely a view issue, and unrelated to the entity...
Reed Copsey
I don't share that opinion. The purpose of the ViewModel is to ready data and perform calculations for the View. The View should be figuring out how to display data, not figuring out how to generate data for display.
Will
Will: The ViewModel should be figuring this out, not the Model. I worded that poorly - my issue was that this is effecting the items in my Model, not in my ViewModel.
Reed Copsey
Heh, I assumed Master _was_ your ViewModel. If you want to be rigid, you could subscribe to the event in your ViewModel. I'm a little more flexible on the issue.
Will