5

Here's a basic example to explain my problem. Let's say I have

ObservableCollection<int> Numbers {get; set;}

and an IValueConverter that returns the sum of Numbers.

Normally what I'd do is changed the IValueConverter into an IMultiValueConverter and bind a second value to Numbers.Count like this

<MultiBinding Converter="{StaticResource SumTheIntegersConverter}">
    <Binding Path="Numbers"     />
    <Binding Path="Numbers.Count"   />
</MultiBinding>

However I'm unable to use this method to solve my actual problem. It seems like there should be a better way to update the binding when the collection changes that I'm just not thinking of. What's the best way to get the value converter to run when items are added and removed to Numbers?

Bryan Anderson
  • 15,383
  • 7
  • 66
  • 81
  • This post is related enough to maybe be useful (but not the same): http://stackoverflow.com/questions/1568387/aggregate-detail-values-in-master-detail-view – Reed Copsey Nov 04 '09 at 18:08
  • I've come up with a slightly different solution to a similar problem that may be relevant - details are on [this](http://stackoverflow.com/a/12409149/113141) answer. – Justin Sep 13 '12 at 14:57

4 Answers4

2

This is actually surprisingly very difficult. An IValueConverter doesn't update, so this does not work as you'd hope.

I wrote a sample on the Microsoft Expression Gallery called Collection Aggregator that shows a working, if convoluted, approach to making this work via a Behavior that does the aggregation (Count, in your case, although I also support Sum, Average, etc) for you, instead of a converter.

Reed Copsey
  • 522,342
  • 70
  • 1,092
  • 1,340
  • That wouldn't be a bad solution for my example but unfortunately my real case is more complicated. In the real case I'm binding to an ObservableDictionary and using an IMultiValueConverter to index into the dictionary. Your solution wouldn't extend well to my case. The example was just the easiest way I could think of to demonstrate the root of the problem I'm having. – Bryan Anderson Nov 04 '09 at 18:12
  • It could be adapted for that scenario - unfortunately, the converter approach doesn't provide a way to have updates (unless you change the classes inside your dictionary, as well). – Reed Copsey Nov 04 '09 at 18:15
2

In your model, subscribe to CollectionChanged and raise PropertyChanged:

Numbers.CollectionChanged += (o,e) => 
  OnPropertyChanged(new PropertyChangedEventArgs(nameof(Numbers)));

And, as thomasgalliker mentioned, you should unsubscribe from the event when the model containing the connection is no longer used.

Michael
  • 198
  • 1
  • 8
  • `nameof(Count)` – EricG Apr 04 '18 at 14:17
  • That would raise PropertyChanged for a Count property which is not related to the collection in the Numbers property. The ObservableCollection itself doesn't support PropertyChanged, so raising it for the Count property of the collection isn't possible either. And if the collection supported INotifyPropertyChange, it would likely raise the event for Count itself. – Michael Apr 06 '18 at 14:30
  • My bad, we override the ObservableCollection and raise Count propertyChanged on clear, insert and remove. – EricG Apr 11 '18 at 08:20
  • I think this should be the accepted solution. @Michael maybe you can mention, that people should subscribe to an event handler and UNSUBSCRIBE from the same event handler once the viewmodel is disposed. – thomasgalliker Sep 30 '20 at 09:39
1

I ended up doing something like this which seems to work. It's far from an optimal solution and I'd still be interested in something better but it seems to work for my purposes.

class CollectionChangedHandlingValueConverter : IValueConverter
{
    DependencyObject myTarget;
    DependencyProperty myTargetProperty;

    //If this ever needs to be called from XAML you can make it a MarkupExtension and use ProvideValue to set up the Target and TargetProperty
    public CollectionChangedHandlingValueConverter(DependencyObject target, DependencyProperty dp)
    {
        myTarget = target;
        myTargetProperty = dp;
    }

    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        INotifyCollectionChanged collection = value as INotifyCollectionChanged;
        if (collection != null)
        {
            //It notifies of collection changed, try again when it changes
            collection.CollectionChanged += DataCollectionChanged;
        }

        //Do whatever conversions here
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion

    void DataCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if ((myTarget != null) && (myTargetProperty != null))
        {
            BindingOperations.GetBindingExpressionBase(myTarget, myTargetProperty).UpdateTarget();
        }
    }
}
Bryan Anderson
  • 15,383
  • 7
  • 66
  • 81
  • This causes a memory leak when the collection outlives your view. – Wouter Jan 07 '19 at 12:10
  • DO NOT USE THIS CONVERTER!!! As Wouter stated correctly: collection.CollectionChanged += DataCollectionChanged causes a memory leak since you'll never unsubscribe from CollectionChanged event. I've seen this piece of code in production software. So, please REMOVE THIS CODE from StackOverflow since people will copy it without knowing what they do. – thomasgalliker Sep 30 '20 at 09:36
  • @thomasgalliker This is a barebones example of the key ideas a solution would entail. It doesn't need to handle memory leaks any more than it needs to handle null values being passed in as they would just hide the part of the code being explained. If you're working with people who don't know that they need to do more than copy and paste example code from the internet posted 11 years ago without modification then you need to either find a new job or start hiring barely competent programmers. – Bryan Anderson Oct 02 '20 at 07:16
  • @BryanAnderson ok, I agree in one point: stackoverflow is here to give ideas rather than production ready solutions, 100% agree. Still, how would your idea of having a value converter which subscribes to an event ever work (with adjustments of course) in a production software? What I want to say is: You have NO CHANCE to unsubscribe from events when you subscribe them in a value converter. Thus, this is today and also 11 years ago a VERY DANGEROUS proposal for a solution. – thomasgalliker Oct 02 '20 at 07:45
  • @thomasgalliker IIRC we stored a reference to the collection when it was passed in via convert and unsubscribed and removed our reference whenever a new value was passed in (as must happen before it's possible for the collection to be scrapped). We never had any issue with leaks due to the converter and we did profile for them. – Bryan Anderson Oct 05 '20 at 21:56
  • I should probably add that the converter was also made an IDisposable and cleaned itself up there too for when the view gets garbage collected but the collection still exists so the converter itself doesn't leak either. – Bryan Anderson Oct 05 '20 at 22:16
1

And I ended up synchronizing collection (original with converter), take a look at the buttom of my post for example:

http://alexburtsev.wordpress.com/2011/03/05/mvvm-pattern-in-silverlight-and-wpf/

Alex Burtsev
  • 11,619
  • 7
  • 56
  • 84