Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Bind Sum of Observable collection in WPF

I am learning WPF so please bare my question if it is very novice! I have a collection of Items and I want to bind that collection to a Grid and bind the Sum to a textbox. Searching online I found this class that will raise event even when we make change to properties of the collection object.

ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)

But I can't seem to make it work in my code.

Here is my code

SaleItem

public class SaleItem : INotifyPropertyChanged
{
    public int Num { get; set; }
    public string ItemID { get; set; }
    public string Name { get; set; }

    private decimal price;
    public decimal Price
    {
        get { return price; }
        set
        {
            this.price = value;
            OnPropertyChanged("Total");
        }
    }
    public int quantity;
    public int Quantity
    {
        get { return quantity; }
        set
        {
            this.quantity = value;
            OnPropertyChanged("Total");
        }
    }

    public decimal Total
    {
        get { return decimal.Round(Price * Quantity, 2, MidpointRounding.AwayFromZero);}
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Sale

public class Sale : INotifyPropertyChanged 
{
    private Decimal _total;
    public TrulyObservableCollection<SaleItem> Items { get; set; }

    public Sale()
    {
        Items = new TrulyObservableCollection<SaleItem>();
        Items.Add(new SaleItem { ItemID = "1", Name = "Product 1", Price = 10, Quantity = 1 });
        Items.Add(new SaleItem { ItemID = "2", Name = "Product 2", Price = 10, Quantity = 1 });
        Items.Add(new SaleItem { ItemID = "3", Name = "Product 3", Price = 10, Quantity = 1 });

    }

    public Decimal Total
    {
        get
        { 
            return Items.Sum(x => x.Total);
        }
        set
        {
            _total = value;
            OnPropertyChanged("Total");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MainWindow

public partial class MainWindow : Window
{
    public Sale Model { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        Model = new Sale();
        this.DataContext = Model;            
    }



    private void btnQuantity_Click(object sender, RoutedEventArgs e)
    {
        Model.Items.Add(new SaleItem { ItemID = "2", Name = "Product 2", Price = 10, Quantity = 1 });

    }
}

XAML

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local ="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">

<Window.DataContext>
    <local:Sale />
</Window.DataContext>

<Grid>
    <StackPanel>
        <DataGrid x:Name="grdItems" ItemsSource="{Binding Items}"></DataGrid>
        <TextBlock x:Name="txtTotal" Text="{Binding Total}"/>
        <Button x:Name="btnQuantity" Content="Update" Click="btnQuantity_Click"/>
    </StackPanel>
</Grid>

I am trying to test adding an item when I click on the button and also updating the quantity of the Item in the grid. If I set a break point and see the value of Total, then it is correct but somehow it is not updating on the UI. What am I missing here?

like image 947
Afraz Ali Avatar asked Oct 07 '15 06:10

Afraz Ali


2 Answers

What you want to do. Is to call OnPropertyChanged when the collection itself has been changed. To recalculate the total. At the moment, the Sale class has no idea that the collection has been updated and that it needs to update its Total property.

One way you can do this is to check for the NotifyCollectionChangedEventlike this:

In your constructor for Sale():

public Sale()
{
 //Instantiate your collection and add items

  Items.CollectionChanged += CollectionChanged;
}

Then add the handler for the event:

public void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
  OnPropertyChanged("Total");
}

If you're never going to do anything else when the collection changes apart from update the Total property. You can shorten the code further by removing the event handler and using a lambda expression instead:

Items.CollectionChanged += (s,e) => OnPropertyChanged("Total");

Unless you're ever planning on explicitly updating the Total yourself in the Sale class. The setter can be removed:

public Decimal Total
{
  get
  {
    return Items.Sum(x => x.Total);
  }
}
like image 74
JBond Avatar answered Nov 11 '22 08:11

JBond


In the Sales.cs add:

public void AddItem(SaleItem item)
    {
        Items.Add(item);
        OnPropertyChanged("Total");
    }

In MainWindow.xaml.cs call:

Model.AddItem(new SaleItem { ItemID = "2", Name = "Product 2", Price = 10, Quantity = 1 });

Instead of

Model.Items.Add(new SaleItem { ItemID = "2", Name = "Product 2", Price = 10, Quantity = 1 });

This will update the Total.

like image 1
aniski Avatar answered Nov 11 '22 08:11

aniski