Search code examples
c#wpfobservablecollection

Trigger InotifyPropertyChanged/CollectionChanged on ObservableCollection


I've tried looking at other topics on this but I haven't found a working implementation to my question. Basically, I have an ObservableCollection called "FruitBasket" that contains different kinds of fruit. FruitBasket itself contains ObservableCollections for each respective type of fruit that passes through so that they can be used as ItemSources for ListViews (Denoted by their names "AppleContainer" and "OrangeContainer"), each displaying one kind of fruit. Because the fruit classes themselves implement INotifyPropertyChanged, modifying their values triggers updates to the ListView controls just fine, however, FruitBasket has a "TotalWeight" property derived from the weights of all the other fruits in the collections. I want "TotalWeight" to update the Label control in the UI without me having to refresh the UI. Triggerering a notification on a property change of the actual ObservableCollection itself, and not simply its constituent members is more difficult and I haven't found any solutions that work so far (or that I've implemented correctly).

public class FruitBasket : ObservableCollection<IFruit>
{
    private decimal _totalWeight;

    public FruitBasket()
    {
        this.Add(new OrangeContainer(this));
        this.Add(new AppleContainer(this));
    }

    public OrangeContainer Oranges
    {
        get { return (OrangeContainer)this.Items[0]; }
    }

    public AppleContainer Apples
    {
        get { return (AppleContainer)this.Items[1]; }
    }

    public decimal TotalWeight
    {
        get { return _totalWeight; }
        set { _totalWeight = value; }
    }

    internal void UpdateWeight(IFruit caller)
    {
        _totalWeight = 0;
        foreach (Orange orng in (OrangeContainer)this.Items[0])
        {
            _totalWeight += orng.Weight;
        }
        foreach (Apple appl in (AppleContainer)this.Items[1])
        {
            _totalWeight += appl.Weight;
        }
    }

Solution

  • You need to call INotifyPropertyChanged.PropertyChanged event of your FruitBasket whenever items are added, removed or Weight property of any item has changed.

    Let's split it into two tasks:

    1. TotalWeight should be recalculated when items are added, removed, or items' weight is changed. We need to handle those events.
    2. Raise FruitBasket.PropertyChanged event

    I have splitted these two tasks into two classes in order to follow Single Responsibility Principle:

    1) - this handles items' PropertyChanged events:

    public abstract class ExtendedObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
    {
        protected override void ClearItems()
        {
            foreach (var item in Items) item.PropertyChanged -= ItemPropertyChanged;
            base.ClearItems();
        }
    
        protected override void InsertItem(int index, T item)
        {
            item.PropertyChanged += ItemPropertyChanged;
            base.InsertItem(index, item);
        }
    
        protected override void RemoveItem(int index)
        {
            this[index].PropertyChanged -= ItemPropertyChanged;
            base.RemoveItem(index);
        }
    
        protected override void SetItem(int index, T item)
        {
            this[index].PropertyChanged -= ItemPropertyChanged;
            item.PropertyChanged += ItemPropertyChanged;
            base.SetItem(index, item);
        }
    
        abstract void ItemPropertyChanged(object sender, PropertyChangedEventArgs e);
    }
    

    2) - this recalculates TotalWeight when necessary

    public class FruitBasket : ExtendedObservableCollection<IFruit>
    {
        protected override void ItemPropertyChanged(object sender, PropertyChangedEventArgs e){
            UpdateWeight();
            OnPropertyChanged("TotalWeight")
        }
    
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            UpdateWeight();
            OnPropertyChanged("TotalWeight")
            base.OnCollectionChanged(e);
        }
    }
    

    Of course your Fruit should implement INotifyPropertyChanged interface. You will find plenty of examples how to do it. It is very simple.