Question

I have an Item class with two properties "quantity" and "price" and implements INotifyPropertyChanged

public class Item:INotifyPropertyChanged
{
    private event PropertyChangedEventHandler _propertyChanged;
    public event PropertyChangedEventHandler PropertyChanged
    {
        add { _propertyChanged += value; }
        remove { _propertyChanged -= value; }
    }

    void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (_propertyChanged != null)
        {
            _propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public int QuantityOnHand
    {
        get
        {
            return this._quantityOnHand;
        }
        set
        {
            if (value > 0)
            {
                this._quantityOnHand = value;
                NotifyPropertyChanged();
            }
            else
            {
                throw new System.ArgumentException("Quantity must be a positive value!");
            }
        }
    }
    .....

}

And I have a collection class of items named "Inventory" with a property of TotalRetailPrice:

public class Inventory {
private List<Item> _inventoryList = new LinkedList<Item>();
public decimal TotalRetailPrice
    {
        get 
        {
            decimal totalRetailPrice = 0M;
            foreach (var item in _inventoryList)
            {
                totalRetailPrice += item.QuantityOnHand * item.RetailPrice;
            }
            return totalRetailPrice;
        }
    }

I am trying to find out a way to automatically update this property TotalRetailPrice, whenever I change either the quantity or the price of any item(s) in the list. How can I do that? Right now with my code, every time I tried to get this totalRetailPrice property, I will have to go through the list and recalculate it.

thanks!

Was it helpful?

Solution

Since the interface INotifyPropertyChanged exposes an event called PropertyChanged you can just subscribe to that in the 'inventory' class.

You will also want to listen for changed events in the list since you will need to know when items are added/removed so you can add/remove event handlers as necessary. I'd suggest using ObservableCollection<T> as this supports some 'collection changed' events. Is there any reason you are using LinkedList<T>?

e.g.

public class Inventory 
{
    private ObservableCollection<Item> _inventoryList = new ObservableCollection<Item>();

    public decimal _total;
    // You probably want INPC for the property here too so it can update any UI elements bound to it
    public decimal Total { get { return _total; } set { _total = value; } }

    // Constructor     
    public Inventory() 
    {
        WireUpCollection();
    }

    private void WireUpCollection()
    {
        // Listen for change events
        _inventoryList.CollectionChanged += CollectionChanged;
    }

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Check what was added - I'll leave this to you, the e.NewItems are the items that
        // have been added, e.OldItems are those that have been removed

        // Here's a contrived example for when an item is added. 
        // Don't forget to also remove event handlers using inpc.PropertyChanged -= Collection_PropertyChanged;
        var inpc = e.NewItems[0] as INotifyPropertyChanged;

        if(inpc != null)
          inpc.PropertyChanged += Collection_PropertyChanged;
    }

    private void Collection_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        RecalculateTotal();
    }

    private void RecalculateTotal()
    { 
        // Your original code here which should feed the backing field
    }
}

Check out the MSDN docs here:

http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx

For info on ObservableCollection<T>. The events section is what you are after. Also note you can use anonymous functions to handle the events if you prefer the syntax or want to capture variables in a certain scope etc. It helps to understand them fully (not sure what's available for Java as I've not really touched it save a couple of Android mess-about projects) so it might be worth reading up as there are a small caveats to be aware of when capturing, but that's another story!

e.g.

_inventoryList.CollectionChanged += (o,e) => 
                                    { 
                                        // Anonymous method body here 
                                        // o = the first param (object sender), e = args (NotifyCollectionChangedEventArgs e)
                                    };

OTHER TIPS

As it was told in another answer in order to "observe" collection items you have to use ObservableCollection which has a special event CollectionChanged. It is raised when the list is changed somehow (item added, removed, replaced). However, that event won't be raised (obviously) when some property of the existing item is changed, for example, inventory.InventoryList[0].QuantityOnHand = 8;. So, to get the solution worked you need to observe both the collection changes (CollectionChanged event) and each collection item changes (PropertyChanged event). Though, implementing that logic correctly is not so easy. The Charlen answer is just a sketch of the solution but not the full solution. It might be easier to use DependenciesTracking lib which solves the issue. See the example below:

public class Item : INotifyPropertyChanged
    {
        private int _quantityOnHand;
        private decimal _retailPrice;

        public event PropertyChangedEventHandler? PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        public int QuantityOnHand
        {
            get => this._quantityOnHand;
            set
            {
                if (_quantityOnHand == value) return;

                if (value <= 0)
                    throw new ArgumentOutOfRangeException(nameof(value), value, "QuantityOnHand must be a positive value");
                _quantityOnHand = value;
                OnPropertyChanged();
            }
        }

        public decimal RetailPrice
        {
            get => _retailPrice;
            set
            {
                if (_retailPrice == value) return;

                if (value <= 0)
                    throw new ArgumentOutOfRangeException(nameof(value), value, "RetailPrice must be a positive value");
                _retailPrice = value;
                OnPropertyChanged();
            }
        }
    }

    public class Inventory : INotifyPropertyChanged
    {
        private static readonly IDependenciesMap<Inventory> _dependenciesMap =
            new DependenciesMap<Inventory>()
                .AddDependency(i => i.TotalRetailPrice,
                    i => i.InventoryList?.Sum(item => item.QuantityOnHand * item.RetailPrice) ?? 0.0m,
                    i => i.InventoryList.EachElement().QuantityOnHand, i => i.InventoryList.EachElement().RetailPrice);

        private ObservableCollection<Item>? _inventoryList;
        private decimal _totalRetailPrice;

        public event PropertyChangedEventHandler? PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        public ObservableCollection<Item>? InventoryList
        {
            get => _inventoryList;
            set
            {
                if (_inventoryList == value) return;
                _inventoryList = value;
                OnPropertyChanged();
            }
        }

        public decimal TotalRetailPrice
        {
            get => _totalRetailPrice;
            private set
            {
                if (value == _totalRetailPrice) return;
                _totalRetailPrice = value;
                OnPropertyChanged();
            }
        }

        public Inventory()
        {
            _dependenciesMap.StartTracking(this);
        }
    }    
    
    public class Tests_SO_20767981
    {
        [SetUp]
        public void Setup()
        {
        }

        [Test]
        public void Test_SO_20767981()
        {
            var inventory = new Inventory();
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));

            inventory.InventoryList = new ObservableCollection<Item>();
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));

            inventory.InventoryList.Add(new Item { QuantityOnHand = 3, RetailPrice = 5 });
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(15));

            inventory.InventoryList.Add(new Item { QuantityOnHand = 1, RetailPrice = 7 });
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(22));

            inventory.InventoryList[0].QuantityOnHand = 8;
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(47));

            inventory.InventoryList[0].RetailPrice = 12;
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(103));

            inventory.InventoryList.RemoveAt(1);
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(96));

            var newInventoryList = new ObservableCollection<Item>
            {
                new Item() { QuantityOnHand = 10, RetailPrice = 0.5m},
                new Item() { QuantityOnHand = 6, RetailPrice = 1.5m}
            };
            inventory.InventoryList = newInventoryList;
            Assert.That(inventory.TotalRetailPrice, Is.EqualTo(14m));
        }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top