Reputation: 93
I am having trouble understanding how to bind the total sale textbox on the UI to the sum of the total price in the datagrid. I have read several articles, tried to implement a sum procedure and bind it, tried using collection changed notification and I can't get the total sale to work. I have an item class that has a Quantity and UnitPrice properties with a calculated TotalPrice which automatically updates when quantity or unit price changes. The list of items is contained in a SalesOrder class. The ui is the main window which I am using to create the order. My code is at: https://github.com/battondl/SalesOrder/tree/master/simpleShoppingListProgram Any guidance on how to get to get the total price on the UI textbox to update automatically would be much appreciated.
Each line item in the datagrid has a total price based on quanity * unit price.
It is the Total Sale price that I am trying to update which is a sum of all the line items totalprice.
class Item : INotifyPropertyChanged
{
private string partNumber;
private int quantity;
private decimal unitPrice;
private DateTime orderDate;
public string PartNumber
{
get { return partNumber; }
set
{
partNumber = value;
OnPropertyChanged();
}
}
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
OnPropertyChanged("Quantity");
OnPropertyChanged("TotalPrice");
}
}
public decimal UnitPrice
{
get { return unitPrice; }
set
{
unitPrice = value;
OnPropertyChanged("UnitPrice");
OnPropertyChanged("TotalPrice");
}
}
public DateTime OrderDate
{
get { return orderDate; }
set
{
orderDate = value;
OnPropertyChanged();
}
}
public decimal TotalPrice => Quantity * UnitPrice;
public Item()
{
PartNumber = "";
Quantity = 0;
UnitPrice = 0.00m;
OrderDate = DateTime.Now;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}
class SalesOrder
{
private string orderNumber;
public string OrderNumber
{
get { return orderNumber; }
set
{
orderNumber = value;
}
}
private ObservableCollection<Item> items;
public ObservableCollection<Item> Items
{
get { return items; }
}
public SalesOrder()
{
orderNumber = "";
items = new ObservableCollection<Item>();
}
public decimal CalculateTotalPrice()
{
decimal total = 0.00m;
foreach (Item item in items)
{
total += item.TotalPrice;
}
return total;
}
}
public partial class MainWindow : Window
{
private SalesOrder salesOrder;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
salesOrder = new SalesOrder();
dgSaleItems.ItemsSource = salesOrder.Items;
tbx_totalSale.Text = salesOrder.CalculateTotalPrice().ToString();
}
}
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="6*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical"
Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Label Content="OrderNumber: " />
<TextBox Name="tbx_orderNumber"
Width="100"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical"
Grid.Row="1">
<DataGrid Name="dgSaleItems"
Grid.Row="1"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="partNumber"
Header="Part Number"
Binding="{Binding Path=PartNumber}" />
<DataGridTextColumn x:Name="quantity"
Header="Quantity"
Binding="{Binding Path=Quantity}" />
<DataGridTextColumn x:Name="unitPrice"
Header="Unit Price"
Binding="{Binding Path=UnitPrice}" />
<DataGridTextColumn x:Name="totalPrice"
Header="Total Price"
Binding="{Binding Path=TotalPrice}" />
<DataGridTemplateColumn x:Name="orderDate"
Header="Order Date">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=OrderDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding Path=OrderDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Orientation="Vertical"
Grid.Row="7">
<StackPanel Orientation="Horizontal">
<Label Content="Total Sale: " />
<TextBox x:Name="tbx_totalSale"
Width="100"/>
</StackPanel>
</StackPanel>
</Grid>
Upvotes: 0
Views: 4020
Reputation: 14477
private void Window_Loaded(object sender, RoutedEventArgs e) { salesOrder = new SalesOrder(); dgSaleItems.ItemsSource = salesOrder.Items; tbx_totalSale.Text = salesOrder.CalculateTotalPrice().ToString(); }
You are only updating the total sale once during the window load event, but you forget you also have to update it every time an item's quantity or price is changed.
Here's how you can do that:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
salesOrder = new SalesOrder();
dgSaleItems.ItemsSource = salesOrder.Items;
salesOrder.Items.CollectionChanged += (s, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Remove)
{
foreach (Item item in args.OldItems)
{
item.PropertyChanged -= UpdateTotalSale;
}
}
else if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (Item item in args.NewItems)
{
item.PropertyChanged += UpdateTotalSale;
}
}
UpdateTotalSale(this, default);
};
}
private void UpdateTotalSale(object sender, PropertyChangedEventArgs e)
{
tbx_totalSale.Text = salesOrder.CalculateTotalPrice().ToString();
}
Ideally, you should implement INPC
on SalesOrder
, and move the above code inside SalesOrder
, as the code behind should be kept as clean as possible.
Upvotes: 0
Reputation: 93
Thanks, I have it working now!
I've modified/added the follow to the SalesOrder class
public decimal TotalSale
{
get { return items.Sum(i => i.TotalPrice); }
}
public SalesOrder()
{
orderNumber = "";
items = new ObservableCollection<Item>();
Items.CollectionChanged += (s, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Remove)
{
foreach (Item item in args.OldItems)
{
item.PropertyChanged -= UpdateTotalSale;
}
}
else if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (Item item in args.NewItems)
{
item.PropertyChanged += UpdateTotalSale;
}
}
};
}
private void UpdateTotalSale(object sender, PropertyChangedEventArgs e)
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(MainWindow))
{
(window as MainWindow).tbx_totalSale.Text = CalculateTotalPrice().ToString();
}
}
}
I also added the following to the xaml.
<StackPanel Orientation="Vertical"
Grid.Row="7">
<StackPanel Orientation="Horizontal">
<Label Content="Total Sale: " />
<TextBox x:Name="tbx_totalSale"
Text="{Binding TotalSale}"
Width="100"
x:FieldModifier="public"/>
</StackPanel>
And finally, this to the code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
salesOrder = new SalesOrder();
dgSaleItems.ItemsSource = salesOrder.Items;
}
Upvotes: 0
Reputation: 137128
There's no binding in your text box:
<TextBox x:Name="tbx_totalSale"
Width="100"/>
It needs to be:
<TextBox x:Name="tbx_totalSale"
Text="{Binding TotalSale}"
Width="100"/>
and you need to add a TotalSale
property to your SaleOrder
class that returns the sum of the lines:
public decimal TotalSale
{
get { return items.Sum(i => i.TotalPrice);
}
You will need to publish an OnPropertyChanged("TotalSale")
event when items are added to or removed from the collection or when then TotalPrice
of a line changes. You are already updating TotalPrice
when either the quantity or unit price changes, so all you need to do is propagate that change upwards through the items
collection.
Upvotes: 1