I have a Xamarin project in which I have a Cart Page. I'm trying to update the data whenever I click on an add or remove button but it doesn't. Here's the code
public class CartViewModel : BindableObject
{
....
private ObservableCollection<OrderDisplay> _ordersList;
public ObservableCollection<OrderDisplay> OrdersList
{
get => _ordersList;
set
{
_ordersList = value;
OnPropertyChanged();
}
}
And then I have AddTotal where I try to update it. It is called when pressing the add or remove button
private async void AddTotal(OrderDisplay oCart, int quantity)
{
decimal total = 0;
int index = 0;
if (oCart != null)
{
foreach (OrderDisplay obj in OrdersList)
{
if (obj.Id == oCart.Id)
{
break;
}
index += 1;
}
OrdersList[index].Quantity = quantity;
OrdersList[index].Total = quantity * OrdersList[index].Price;
//OrdersList = null;
//OrdersList = tempOrdersList;
var order = await _apiService.GetOrderById(OrdersList[index].Id, token);
order.Data.Quantity = OrdersList[index].Quantity;
var orderUpdated = await _apiService.UpdateOrder(Convert.ToString(order.Data.Id), token, order.Data);
if (!orderUpdated)
{
await _messageService.ShowMessageAsync("Error", "Ha ocurrido un error.", "Ok", "");
return;
}
}
foreach (OrderDisplay order in OrdersList)
{
total = order.Total + total;
}
LblTotalCart = string.Format("{0:N2}€", total);
}
For context here is the view
I don't know how to do it. Please help.
EDIT
I tried doing it with INotifyPropertyChanged but gives me NullReference. I don't know if this is correct
public class OrderDisplay : INotifyPropertyChanged
{
//public int Id { get; set; }
//public int Quantity { get; set; }
//public decimal Price { get; set; }
//public decimal Total { get; set; }
//public CrProduct Product { get; set; }
private int id;
public int Id
{
get { return id; }
set
{
id = value;
OnPropertyChanged("Id");
}
}
public int quantity;
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
OnPropertyChanged("Quantity");
}
}
public decimal price;
public decimal Price
{
get { return price; }
set
{
price = value;
OnPropertyChanged("Price");
}
}
public decimal total;
public decimal Total
{
get { return total; }
set
{
total = value;
OnPropertyChanged("Total");
}
}
public CrProduct product;
public CrProduct Product
{
get { return product; }
set
{
product = value;
OnPropertyChanged("Product");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
INotifyPropertyChanged is the mechanism that the UI uses to determine when it needs to update a bound property. If you are changing property P on class X, then X needs to implement INotifyPropertyChanged and raise a PropertyChanged event when P is modified.
an ObservableCollection<T> only raises events when items are removed or added from the collection. It does not raise events when individual properties on the class T are modified.
Related
I have a simple app that stores different stock data, my add stock button opens a new window with a form and when I press save I add the stock to the database but the datagrid doesn't get updated
I access the static instance of my main Viewmodel from my EntryViewModel via:
Main View Model(StockViewModel)
public class StockViewModel:INotifyPropertyChanged
{
#region Fields
private IList<Stock> _stockList;
private StatsBuilder _statsBuilder = new StatsBuilder();
public Stock stock = new Stock();
public Stock10 stock10 = new Stock10();
public Stock20 stock20 = new Stock20();
private ICommand _searchCommand;
private static StockViewModel _instance = new StockViewModel();
public static StockViewModel Instance { get { return _instance; } }
public IList<Stock> Stocks
{
get { return _stockList; }
set { _stockList = value; OnPropertyChanged(nameof(Stocks)); }
}
#endregion
#region Constructor
public StockViewModel()
{
using (var stocks = new AppDbContext())
{
Stocks = stocks.LowFloatStocks.ToList();
}
}
#endregion
#region Day1's
private string _ticker;
public string Ticker
{
get { return _ticker; }
set
{
_ticker = value;
OnPropertyChanged("Ticker");
}
}
private DateTime _date = DateTime.Now;
public DateTime Date
{
get { return _date; }
set { _date = value; }
}
private decimal _preivousClose;
public decimal PreviousClose
{
get { return _preivousClose; }
set { _preivousClose = value; OnPropertyChanged("PreviousClose"); }
}
private decimal _pmOpeningPrice;
public decimal PM_OpeningPrice
{
get { return _pmOpeningPrice; }
set { _pmOpeningPrice = value; OnPropertyChanged("PM_OpeningPrice"); }
}
private decimal _openingPrice;
public decimal OpeningPrice
{
get { return _openingPrice; }
set { _openingPrice = value; OnPropertyChanged("OpeningPrice"); }
}
private decimal _high;
public decimal High
{
get { return _high ; }
set { _high = value; OnPropertyChanged("High"); }
}
private decimal _low;
public decimal Low
{
get { return _low; }
set { _low = value; OnPropertyChanged("Low"); }
}
private decimal _close;
public decimal Close
{
get { return _close; }
set { _close = value; OnPropertyChanged("Close"); }
}
private string _catalyst;
public string Catalyst
{
get { return _catalyst; }
set { _catalyst = value; OnPropertyChanged("Catalyst"); }
}
private decimal _float;
public decimal Float
{
get { return _float; }
set {_float = value; OnPropertyChanged("Float"); }
}
private string _dilution;
public string Dilution
{
get { return _dilution; }
set { _dilution = value; OnPropertyChanged("Dilution"); }
}
#endregion
#region Day2's
//public decimal Day2Open
//{
// //get { return stock.Day2Open; }
// set { stock.Day2Open = value; OnPropertyChanged("Day2Open"); }
//}
//public decimal Day2High
//{
// get { return stock.Day2High; }
// set { stock.Day2High = value; OnPropertyChanged("Day2High"); }
//}
//public decimal Day2Low
//{
// get { return stock.Day2Low; }
// set { stock.Day2Low = value; OnPropertyChanged("Day2Low"); }
//}
//public decimal Day2Close
//{
// get { return stock.Day2Close; }
// set { stock.Day2Close = value; OnPropertyChanged("Day2Close"); }
//}
#endregion
#region Stats
private Stock _selectedstock;
public Stock SelectedStock
{
private get
{
return _selectedstock;
}
set
{
_selectedstock = value;
OnPropertyChanged("SelectedStock");
GetStats(_selectedstock.Ticker);
}
}
public int Gaps10 { get { return stock10.Gaps10; } set { stock10.Gaps10 = value; OnPropertyChanged("Gaps10"); } }
public decimal AvgGap10 { get {return stock10.AvgGap10; } set { stock10.AvgGap10 = value; OnPropertyChanged("AvgGap10"); } }
public int CloseGreen10 { get { return stock10.CloseGreen10; } set { stock10.CloseGreen10 = value; OnPropertyChanged("CloseGreen10"); } }
public decimal CloseGreenPercent10 { get { return stock10.CloseGreenPercent10; } set { stock10.CloseGreenPercent10 = value; OnPropertyChanged("CloseGreenPercent10"); } }
public int CloseRed10 { get { return stock10.CloseRed10; } set { stock10.CloseRed10 = value; OnPropertyChanged("CloseRed10"); } }
public decimal CloseRedPercent10 { get { return stock10.CloseRedPercent10; } set { stock10.CloseRedPercent10 = value; OnPropertyChanged("CloseRedPercent10"); } }
public decimal AvgSpike10 { get; set; }
public decimal Downside10 { get; set; }
public int PMFade10 { get { return stock10.PMFade10; } set { stock10.PMFade10 = value; OnPropertyChanged("PMFade10"); } }
public decimal PMFadePercent10 { get { return stock10.PMFadePercent10; } set { stock10.PMFadePercent10 = value; OnPropertyChanged("PMFadePercent10"); } }
public int Gaps20 { get {return stock20.Gaps20 ; } set { stock20.Gaps20 = value; OnPropertyChanged("Gaps20"); } }
public decimal AvgGap20 { get { return stock20.AvgGap20; } set { stock20.AvgGap20 = value; OnPropertyChanged("AvgGap20"); } }
public int CloseGreen20 { get { return stock20.CloseGreen20; } set { stock20.CloseGreen20 = value; OnPropertyChanged("CloseGreen20"); } }
public decimal CloseGreenPercent20 { get { return stock20.CloseGreenPercent20; } set { stock20.CloseGreenPercent20 = value; OnPropertyChanged("CloseGreenPercent20"); } }
public int CloseRed20 { get { return stock20.CloseRed20; } set { stock20.CloseRed20 = value; OnPropertyChanged("CloseRed20"); } }
public decimal CloseRedPercent20 { get { return stock20.CloseRedPercent20; } set { stock20.CloseRedPercent20 = value; OnPropertyChanged("CloseRedPercent20"); } }
public decimal AvgSpike20 { get; set; }
public decimal Downside20 { get; set; }
public int PMFade20 { get { return stock20.PMFade20; } set { stock20.PMFade20 = value; OnPropertyChanged("PMFade20"); } }
public decimal PMFadePercent20 { get { return stock20.PMFadePercent20; } set { stock20.PMFadePercent20 = value; OnPropertyChanged("PMFadePercent20"); } }
#endregion
#region PropertyChange
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Commands
public ICommand SearchCommand => _searchCommand ?? (_searchCommand = new RelayCommand(param => this.SearchStock(param)));
#endregion
#region Actions
private void SearchStock(object _ticker)
{
try
{
var stock = Stocks.First(x => x.Ticker.Trim() == _ticker.ToString());
_selectedstock = (Stock)stock;
GetStats(_ticker.ToString());
}
catch (Exception)
{
MessageBox.Show("No matching Ticker");
}
}
private void GetStats(string ticker)
{
//Stocks with 10% gap ups
stock10 = _statsBuilder.GetStats10(ticker);
stock20 = _statsBuilder.GetStats20(ticker);
Gaps10 = stock10.Gaps10;
AvgGap10 = stock10.AvgGap10;
CloseGreen10 = stock10.CloseGreen10;
CloseGreenPercent10 = stock10.CloseGreenPercent10;
CloseRed10 = stock10.CloseRed10;
CloseRedPercent10 = stock10.CloseRedPercent10;
PMFade10 = stock10.PMFade10;
PMFadePercent10 = stock10.PMFadePercent10;
//Stock with 20% gap ups
Gaps20 = stock20.Gaps20;
AvgGap20 = stock20.AvgGap20;
CloseGreen20 = stock20.CloseGreen20;
CloseGreenPercent20 = stock20.CloseGreenPercent20;
CloseRed20 = stock20.CloseRed20;
CloseRedPercent20 = stock20.CloseRedPercent20;
PMFade20 = stock20.PMFade20;
PMFadePercent20 = stock20.PMFadePercent20;
}
#endregion
}
When I click the add stock button it fires the AddCommand which in turn fires the Addstock method, this adds the stock to the database and updates the list in the StockViewModel instance but it does not update the datagrid OnPropertyChanged for some reason
EntryViewModel:
public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(param => this.AddStock()));
#endregion
#region PropertyChange
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Methods
private void AddStock()
{
using (var stocks = new AppDbContext())
{
stock.Id = 0;
stock.Ticker = _ticker;
stock.PreviousClose = _preivousClose;
stock.PM_OpeningPrice = _pmOpeningPrice;
stock.OpeningPrice = _openingPrice;
stock.High = _high;
stock.Low = _low;
stock.Close = _close;
stock.Catalyst = _catalyst;
stock.Float = _float;
stock.Dilution = _dilution;
stocks.LowFloatStocks.Add(stock);
stocks.SaveChanges();
StockViewModel.Instance.Stocks = stocks.LowFloatStocks.ToList();
Clear();
}
}
Data Grid Binding;
<DataGrid Name="StockGrid" Margin="5,5,5,5" Height="250" ItemsSource="{Binding Stocks}" IsReadOnly="True" AutoGeneratingColumn ="StockGrid_AutoGeneratingColumn" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto" SelectedItem="{Binding SelectedStock, Mode=TwoWay}">
</DataGrid>
Data Context of StockView Model:
<Window.DataContext> <local1:StockViewModel/> </Window.DataContext>
Any suggestions?
The following sets the DataContext to an new instance of the StockViewModel which is not the same as the instance returned by the Instance property:
<Window.DataContext> <local1:StockViewModel/> </Window.DataContext>
The fact that you can do this means that StockViewModel is not really a singleton. You should add a private constuctor to it to make it a real singleton if this is what you want:
private StockViewModel() { }
You would then set the DataContext to the instance returned by the Instance property:
<Window.DataContext>
<x:Static Member="this:StockViewModel.Instance" />
</Window.DataContext>
I have an application that use the MVVM pattern and I would like to implement validation when the user is fill the information. For example if min is greater than max I want to disable the button in my view.
I have implemented IDataErrorInfo in the model. Here is my model:
public class MyModel : IDataErrorInfo, INotifyPropertyChanged
{
private string stock;
public string Stock
{
get { return stock; }
set { stock = value; }
}
private double min;
public double Min
{
get { return min; }
set { min = value; OnPropertyChange("Min"); }
}
private double max;
public double Max
{
get { return max; }
set { max = value; OnPropertyChange("Max"); }
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string error = string.Empty;
if (columnName == "Min" || columnName == "Max")
{
if (Min > Max)
{
error = "Min can't be greater than Max";
}
}
return error;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
And my ViewModel has a collection of MyModel (Bound to DataGrid) and a boolean property called CanUpdate. This CanUpdate is bound to IsEnabled Property of update button in my view.
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyModel> stocks;
public ObservableCollection<MyModel> Stocks
{
get { return stocks; }
set { stocks = value; }
}
private bool canUpdate = true;
public bool CanUpdate
{
get { return canUpdate; }
set { canUpdate = value; }
}
public MyViewModel()
{
AddStocks();
}
public void AddStocks()
{
Stocks = new ObservableCollection<MyModel>();
Stocks.Add(new MyModel() { Stock ="Stock 1", Min = 10, Max=20 });
Stocks.Add(new MyModel() { Stock = "Stock 2", Min = 5, Max = 15 });
Stocks.Add(new MyModel() { Stock = "Stock 3", Min = 6, Max = 25 });
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
User can edit the Min and Max value from the DataGrid. While editing whenever a error raised in MyModel has to update the CanUpdate property in MyViewModel. Please guide me to achieve this.
In the view model, when adding items to the collection hook to their PropertyChanged event. In a handler analyse the Error property and modify CanUpdate accordingly.
I populate a data grid with a list of objects that come from a repository like this:
public static IEnumerable<Order> GetOrdersForDataGrid()
{
IEnumerable<Order> query;
using (RSDContext = new RSDContext())
{
query = context.Orders.Include(o=>o.OrderDetails).ToList();
}
return query;
}
When I want to edit an order I pass the selected row to a new window like this:
OrderEditWindow orderEdit = new OrderEditWindow();
orderEdit.SelectedOrder = SelectedOrder;
orderEdit.ShowDialog();
Here I set the DataContext of the Window to:
DataContext = SelectedOrder;
In this window I have another data grid that binds to OrderDetails collection property of Order. The problem is on CRUD operations on OrderDetails. For example, after I add a new orderDetail like this:
private void AddProductDetailButton_OnClick(object sender, RoutedEventArgs e)
{
if (!ValidateProductDetail())
return;
var _selectedProduct = ProductAutoCompleteBox.SelectedItem as Product;
var selectedProduct = ProductsRepository.GetProductById(_selectedProduct.ProductId);
OrderDetail orderDetail = new OrderDetail();
orderDetail.Price = selectedProduct.Price;
orderDetail.ProductCode = selectedProduct.Code;
orderDetail.ProductName = selectedProduct.Name;
orderDetail.Quantity = int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Um = selectedProduct.Um;
orderDetail.Total = selectedProduct.Price * int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Group = selectedProduct.Subgroup.Group.Name;
orderDetail.Subgroup = selectedProduct.Subgroup.Name;
orderDetail.SupplierName = selectedProduct.Supplier.Name;
//orderDetail.Order=SelectedOrder;
//orderDetail.OrderId = SelectedOrder.OrderId;
SelectedOrder.OrderDetails.Add(orderDetail);
ProductAutoCompleteBox.Text = string.Empty;
QuantityNumericUpDown.Value = 1;
ProductAutoCompleteBox.Focus();
}
and then I call the update method from repository:
public static void UpdateOrder(Order order)
{
using (RSDContext context = new RSDContext())
{
context.Orders.Attach(order);
context.Entry(order).State = EntityState.Modified;
context.SaveChanges();
}
}
I get an error about OrderId. If i set manualy the navigation property and the id I don't get an error but changes dont get saved into db.
My Order model look like this:
public class Order : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Order()
{
_OrderDetails = new ObservableCollection<OrderDetail>();
_OrderDetails.CollectionChanged += _OrderDetails_CollectionChanged;
}
void _OrderDetails_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
AttachProductChangedEventHandler(e.NewItems.Cast<OrderDetail>());
if (e.OldItems != null)
CalcualteTotals();
}
[NotMapped]
public decimal CalculatedTotal
{
get
{
return OrderDetails.Sum(x => x.Total);
}
}
public int OrderId { get; set; }
private int _Number;
public int Number
{
get { return _Number; }
set
{
_Number = value;
NotifyPropertyChanged("Number");
}
}
private DateTime _Date;
public DateTime Date
{
get { return _Date; }
set
{
_Date = value;
NotifyPropertyChanged("Date");
}
}
private bool _Canceled;
public bool Canceled
{
get { return _Canceled; }
set
{
_Canceled = value;
NotifyPropertyChanged("Canceled");
}
}
private string _ClientName;
public string ClientName
{
get { return _ClientName; }
set
{
_ClientName = value;
NotifyPropertyChanged("ClientName");
}
}
private string _ClientPhone;
public string ClientPhone
{
get { return _ClientPhone; }
set
{
_ClientPhone = value;
NotifyPropertyChanged("ClientPhone");
}
}
private string _DeliveryAddress;
public string DeliveryAddress
{
get { return _DeliveryAddress; }
set
{
_DeliveryAddress = value;
NotifyPropertyChanged("DeliveryAddress");
}
}
private decimal _Transport;
public decimal Transport
{
get { return _Transport; }
set
{
_Transport = value;
NotifyPropertyChanged("Transport");
}
}
private decimal _Total;
public decimal Total
{
get { return _Total; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
private ObservableCollection<OrderDetail> _OrderDetails;
public virtual ObservableCollection<OrderDetail> OrderDetails
{
//get { return _OrderDetails ?? (_OrderDetails = new ObservableCollection<OrderDetail>()); }
get
{
return _OrderDetails;
}
set
{
_OrderDetails = value;
NotifyPropertyChanged("OrderDetails");
}
}
private void AttachProductChangedEventHandler(IEnumerable<OrderDetail> orderDetails)
{
foreach (var p in orderDetails)
{
p.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case "Quantity":
case "Price":
case "Total":
CalcualteTotals();
break;
}
};
}
CalcualteTotals();
}
public void CalcualteTotals()
{
NotifyPropertyChanged("CalculatedTotal");
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
And my OrderDetail model look like this:
public class OrderDetail : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
private int _ProductCode;
public int ProductCode
{
get { return _ProductCode; }
set
{
_ProductCode = value;
NotifyPropertyChanged("ProductCode");
}
}
private string _ProductName;
public string ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
NotifyPropertyChanged("ProductName");
}
}
private string _Um;
public string Um
{
get { return _Um; }
set
{
_Um = value;
NotifyPropertyChanged("Um");
}
}
private decimal _Price;
public decimal Price
{
get { return _Price; }
set
{
_Price = value;
NotifyPropertyChanged("Price");
NotifyPropertyChanged("Total");
}
}
private int _Quantity;
public int Quantity
{
get { return _Quantity; }
set
{
_Quantity = value;
NotifyPropertyChanged("Quantity");
NotifyPropertyChanged("Total");
}
}
private string _SupplierName;
public string SupplierName
{
get { return _SupplierName; }
set
{
_SupplierName = value;
NotifyPropertyChanged("SupplierName");
}
}
private string _Subgroup;
public string Subgroup
{
get { return _Subgroup; }
set
{
_Subgroup = value;
NotifyPropertyChanged("Subgroup");
}
}
private string _Group;
public string Group
{
get { return _Group; }
set
{
_Group = value;
NotifyPropertyChanged("Group");
}
}
public decimal _Total;
public decimal Total
{
get { return Quantity * Price; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
I'm really trying to use some sort of unit of work and I don't understand how i'm supposed to apply CRUD on objects with child collections and keep the UI updated in the same time (by working in a ObservableCollection and using Binding ClientPhone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged my parent window is updated as I type)
A final working solution:
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
List<int> OriginalOrderDetailsIds =
context.OrderDetails.Where(o => o.OrderId == order.OrderId).Select(o => o.OrderDetailId).ToList();
List<int> CurrentOrderDetailsIds = details.Select(o => o.OrderDetailId).ToList();
List<int> DeletedOrderDetailsIds = OriginalOrderDetailsIds.Except(CurrentOrderDetailsIds).ToList();
context.Entry(order).State = EntityState.Modified;
foreach (var deletedOrderDetailId in DeletedOrderDetailsIds)
{
context.Entry(context.OrderDetails.Single(o => o.OrderDetailId == deletedOrderDetailId)).State = EntityState.Deleted;
}
foreach (OrderDetail detail in details)
{
// Add.
if (detail.OrderDetailId == 0)
{
detail.OrderId = order.OrderId;
context.Entry(detail).State = EntityState.Added;
}
// Update.
else
{
context.Entry(detail).State = EntityState.Modified;
}
}
context.SaveChanges();
}
You could do this way for adding and updating the child, but not sure about deleted order details in the ui. If you don't want to get the order from entity, you need some kind of marking in the OrderDetail for deleted OrderDetail.
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
context.Entry(order).State = EntityState.Modified;
foreach (var detail in details)
{
if (detail.Id == 0)
{
// Adds.
detail.OrderId = order.Id;
context.Entry(detail).State = EntityState.Added;
}
else if (detail.IsDeleted)
// Adds new property called 'IsDeleted'
// and add [NotMapped] attribute
// then mark this property as true from the UI for deleted items.
{
// Deletes.
context.Entry(detail).State = EntityState.Deleted;
}
else
{
// Updates.
context.Entry(detail).State = EntityState.Modified;
}
}
order.OrderDetails = details;
context.SaveChanges();
}
My questions is similar to this INotifyPropertyChanged and calculated property
I'm reusing some code from the above example because it's easier to understand what's going on.
Assume I started with something similar to this. Note that INotifyPropertyChangedBase is the base class that I use to implement INotifyPropertyChanged.
public class Order : INotifyPropertyChangedBase
{
private string itemName;
public string ItemName
{
get { return itemName; }
set
{
itemName = value;
}
}
private decimal itemPrice;
public decimal ItemPrice
{
get { return itemPrice; }
set
{
itemPrice = value;
}
}
private int quantity;
public int Quantity
{
get { return quantity; }
set
{
quantity= value;
OnPropertyChanged("Quantity");
OnPropertyChanged("TotalPrice");
}
}
public decimal TotalPrice
{
get { return ItemPrice * Quantity; }
}
}
After generating similar code to this I realized that each order could be comprised of multiple Items so I generated a class : Item similar to this:
public class Item : INotifyPropertyChangedBase
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private decimal price;
public decimal Price
{
get { return price; }
set { price = value; }
}
private int quantity;
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
OnPropertyChanged("Quantity");
}
}
}
Then I converted my Order class to look like this.
public class Order : INotifyPropertyChangedBase
{
private ObservableCollection<Item> itemInfo;
public ObservableCollection<Item> ItemInfo
{
get { return itemInfo; }
set
{
itemInfo = value;
OnPropertyChanged("ItemInfo");
OnPropertyChanged("TotalPrice");
}
}
public decimal TotalPrice
{
get
{
Decimal totalPrice;
foreach (Item item in ItemInfo)
{
totalPrice += item.Quantity * item.Price;
}
return totalPrice;
}
}
}
Implementing this is done via a DataGrid. Each Order is a row. I am binding column headers to Item.Name (limited number of options) and Item.Quantity to the appropriate column cell. The final column is the TotalPrice.
Previously, the TotalPrice would update when I changed the Quantity. Now, with the new implementation using Item the TotalPrice will not update in the DataGrid. It seems that the setter for ItemInfo will not fire when I update a instance of Item.Quantity. The setter on Item.Quantity does fire when I update the appropriate DataGrid cell.
How do I get the value of a read-only property (TotalPrice) to update using nested properties (Item)?
You will have to listen CollectionChanged of ItemInfo like
public class Order : INotifyPropertyChangedBase
{
public Order()
{
ItemInfo =new ObservableCollection<Item>();
ItemInfo.CollectionChanged += ItemInfo_CollectionChanged;
}
void ItemInfo_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("TotalPrice");
}
private ObservableCollection<Item> itemInfo;
public ObservableCollection<Item> ItemInfo
{
get { return itemInfo; }
set
{
itemInfo = value;
OnPropertyChanged("ItemInfo");
OnPropertyChanged("TotalPrice");
}
}
OR
public class Order : INotifyPropertyChangedBase
{
void ItemInfo_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("TotalPrice");
}
private ObservableCollection<Item> itemInfo;
public ObservableCollection<Item> ItemInfo
{
get { return itemInfo; }
set
{
if(itemInfo!=null)
itemInfo.CollectionChanged -= ItemInfo_CollectionChanged;
itemInfo = value;
if(itemInfo!=null)
itemInfo.CollectionChanged += ItemInfo_CollectionChanged;
OnPropertyChanged("ItemInfo");
OnPropertyChanged("TotalPrice");
}
}
I am using a DataGrid and is bind using an ObservableCollection in the ViewModel
private ObservableCollection<StockItem> _stockList;
public ObservableCollection<StockItem> StockList
{
get
{
return _stockList;
}
set
{
_stockList = value;
OnPropertyChanged("StockList");
}
}
The StockItem class contains its Properties which are the Column in DataGrid.
There is a column named Amount in DataGrid whose values changed with Quantity*Price Column of the same datagrid.
I have a Property called TotalAmount in the ViewModel which is calculated in the ObservableCollection CollectionChanged event like
void OnStockListChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
this.TotalAmount = this.StockList.Sum(t => t.Amount);
}
This values is only updated in the TextBox bind to TotalAmount when a new row is added to a DataGrid with some data. I want this TextBox of TotalAmount to be updated as soon as the Amount Column in the datagrid changes.
How can i do this.
StockItem Class
public class StockItem : ObservableObject, ISequencedObject
{
JIMSEntities dbContext = new JIMSEntities();
public StockItem()
{
var qs = dbContext.Stocks.Select(s => s.StockName);
_stocks = new CollectionView(qs.ToArray());
_stocks.CurrentChanged += new EventHandler(Stocks_CurrentChanged);
}
void Stocks_CurrentChanged(object sender, EventArgs e)
{
if (_stocks.CurrentItem != null)
StockName = _stocks.CurrentItem.ToString();
var qs = (from p in dbContext.Stocks
where p.StockName.Contains(StockName)
select new { Unit = p.Unit, UnitPrice = p.UnitPrice }).SingleOrDefault();
if (qs != null)
{
Unit = qs.Unit;
UnitPrice = (decimal)qs.UnitPrice;
}
}
private CollectionView _stocks;
public CollectionView Stocks
{
get
{
return _stocks;
}
set
{
_stocks = value;
OnPropertyChanged("Stocks");
}
}
private int _sNo;
public int SNo
{
get
{
return _sNo;
}
set
{
_sNo = value;
OnPropertyChanged("SNo");
}
}
private string _stockName;
public string StockName
{
get
{
return _stockName;
}
set
{
_stockName = value;
OnPropertyChanged("StockName");
}
}
private decimal _unitPrice;
public decimal UnitPrice
{
get
{
return _unitPrice;
}
set
{
_unitPrice = value;
OnPropertyChanged("UnitPrice");
OnPropertyChanged("Amount");
}
}
private string _unit;
public string Unit
{
get
{
return _unit;
}
set
{
_unit = value;
OnPropertyChanged("Unit");
}
}
private decimal _discount;
public decimal Discount
{
get
{
return _discount;
}
set
{
_discount = value;
OnPropertyChanged("Discount");
OnPropertyChanged("Amount");
}
}
private decimal _quantity;
public decimal Quantity
{
get
{
return _quantity;
}
set
{
_quantity = value;
OnPropertyChanged("Quantity");
OnPropertyChanged("Amount");
}
}
public decimal Amount
{
get
{
decimal total = Quantity * (UnitPrice - (UnitPrice * (Discount / 100)));
return total;
}
}
public override string ToString()
{
return StockName;
}
}
so, basically, what you are seeing is due to a common mis-conception about ObservableCollection. OC does NOT notify when objects that it contains are changed. It notifies when IT changes (take a look at INotifyCollectionChanged)--when items are added, removed, etc.
what you want to do is be notified when a StockItem that is contained in the OC changes. You are going to have to do a couple of things
1) make sure INotifyPropertyChanged (which you say you are already doing) is implemented on your StockItem
2) customize or find an implementation of ObservableCollection that will notify when an item contained in the collection changes (here is one)
You need to subscribe to PropertyChanged of all the items in the collection to also recalculate the value if the Amount changes on any of the items, it's a bit messy. Someone somewhere might have written utility classes for that...