ObservableCollection CollectionChanged not helpful in WPF MVVM - c#

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...

Related

Xamarin - Update Observable Collection Not Working

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.

Little complex Databinding in windows form combobox

I am in trouble while working on Databinding in windows form. I have two classes, one is Project and another is Update. Now all project object is having a list of Updates and it is binded to a combobox, but when the user changes selection need to display/bind the properties of Update object to another controls. But is not updating as expected, when the user changes selection. Please help me on this..
Screenshot
See my class and codes below,
public class Project
{
private int _id;
private string _name;
public Project(int id, string name)
{
_id = id;
_name = name;
ReadUpdates();
}
public List<Update> AvailableUpdates { get; set; }
public int Id { get { return _id; } }
public string ProjName
{
get { return _name; }
set { _name = value; }
}
private void ReadUpdates()
{
AvailableUpdates = new List<Update>();
for (int i = 0; i < 10; i++)
{
AvailableUpdates.Add(new
Update(i, DateTime.Now.AddDays(i)));
}
}
}
public class Update
{
private string _title;
private int _uid;
private DateTime _updatedOn;
public Update(int id, DateTime updatedOn)
{
_title = $"Update:{id}";
_uid = id;
_updatedOn = updatedOn;
}
public string Title
{
get { return _title; }
set { _title = value; }
}
public int UId
{
get { return _uid; }
set { _uid = value; }
}
public DateTime UpdatedOn
{
get { return _updatedOn; }
set { _updatedOn = value; }
}
}
public partial class Form1 : Form
{
private Update _currentUpdate;
private Project _project;
public Form1()
{
InitializeComponent();
_project = new Project(1, "Sample Project");
DoBindings();
}
private void DoBindings()
{
NameBox.DataBindings.Add("Text", _project, "ProjName");
IdBox.DataBindings.Add("Text", _project, "Id");
UpdatesCombo.DataSource = _project.AvailableUpdates;
UpdatesCombo.DisplayMember = "UId";
_currentUpdate = (Update)UpdatesCombo.SelectedItem;
UpdateTitle.DataBindings.Add("Text", _currentUpdate, "Title");
UpdateDate.DataBindings.Add("Value", _currentUpdate, "UpdatedOn");
}
private void UpdatesCombo_SelectionChangeCommitted(object sender, System.EventArgs e)
{
_currentUpdate = (Update)UpdatesCombo.SelectedItem;
}
}
Please correct me if I am wrong.
It's quite simple. All you need is to bind the related controls to the same list data source as the combo box.
var updates = _project.AvailableUpdates;
UpdatesCombo.DataSource = updates;
UpdatesCombo.DisplayMember = "UId";
UpdateTitle.DataBindings.Add("Text", updates, "Title");
UpdateDate.DataBindings.Add("Value", updates, "UpdatedOn");
The data binging infrastructure creates CurrencyManager class per each unique list data source. The non list controls are actually bound to the Current property, which is updated by the combo box selection.

Accessing instance of generic collection from items in collection

I have a generic collection which is representative of an order with each line item being an "Item" class object. The order's total value is based off of the combined values of all the line items. I currently have a way of getting the total value on an order whenever the "TotalValue" property is retrieved, but I find this method somewhat inelegant because it feels like Schrodinger's variable; it only becomes the total value when observed.
Is there any way that I can get an "Item" member held inside a "Quote" collection to trigger the "updateValue" method of the instance of the Quote class containing it whenever the "Item" object goes to modify its own cost values? The item class automatically modifies its own "TotalCost" but I want it to kick it up the chain and modify the order's total cost/value at the same time.
I've tried to pare it down to keep only the relevant bits.
namespace Arbitrary
{
public class Order<T> : IEnumerable<T> where T : Item
{
private List<T> _lines = new List<T>();
private decimal _totalValue;
public decimal TotalValue
{
get { return updateValue(); }
}
private decimal updateValue()
{
_totalValue = 0;
foreach(T Item in _lines)
{
_totalValue += Item.TotalCost;
}
return _totalValue;
}
}
public class Item
{
private decimal _cost;
private int _quantity;
private decimal _totalCost;
public decimal Cost
{
get { return _cost; }
set
{
_cost = value;
_totalCost = (_cost * _quantity);
}
}
public int Quantity
{
get { return _quantity; }
set
{
_quantity = value;
_totalCost = (_cost * _quantity);
}
}
public decimal TotalCost
{
get { return _totalCost; }
}
}
}
Update: Figured it out thanks to the tip from Blorgbeard. I switched the constructor of "Item" to take the object reference of the Quote collection (since they'll never exist in absence of a Quote collection anyway). I changed the "updateValue" method to internal in order for the Item class to access it since they're both in the same assembly. The updated changes are commented with "// modified"
namespace Arbitrary
{
public class Order<T> : IEnumerable<T> where T : Item
{
private List<T> _lines = new List<T>();
private decimal _totalValue;
public decimal TotalValue
{
get { return updateValue(); }
}
internal decimal updateValue() // modified
{
_totalValue = 0;
foreach(T Item in _lines)
{
_totalValue += Item.TotalCost;
}
return _totalValue;
}
}
public class Item
{
private decimal _cost;
private int _quantity;
private decimal _totalCost;
private Quote<Item> q; // modified
public Item(Quote<Item> q) // modified
{
this.q = q; // modified
}
public decimal Cost
{
get { return _cost; }
set
{
_cost = value;
_totalCost = (_cost * _quantity);
q.updateValue(); // modified
}
}
public int Quantity
{
get { return _quantity; }
set
{
_quantity = value;
_totalCost = (_cost * _quantity);
q.updateValue(); // modified
}
}
public decimal TotalCost
{
get { return _totalCost; }
}
}
}

How to fire INotifyPropertyChanged on a read-only property that uses nested properties to calculate a value

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");
}
}

Why Datetime property value changes when I retrieve it from a List?

I have a class called Job and that encapsulates an object of another class called SelectedItem which has a DateTime property called ItemDate;
class Job
{
private SelectedItem m_SelectedItem = null;
public Job() { }
public SelectedItem Item
{
get { return m_SelectedItem; }
set { m_SelectedItem = value; }
}
}
class SelectedItem
{
DateTime m_ItemDate = default(DateTime);
public SelectedItem() { }
public DateTime ItemDate
{
get
{
return (m_ItemDate == default(DateTime))
? DateTime.Now
: m_ItemDate;
}
set { m_ItemDate = value; }
}
}
Then I have a JobManager class that has a List of Job: JobQueue;
class JobManager
{
private ICollection<Job> m_JobQueue = null;
public JobManager()
{
m_JobQueue = new List<Job>();
}
public ReadOnlyCollection<Job> JobQueue
{
get { return m_JobQueue.ToList().AsReadOnly(); }
}
private void CreateJob(SelectedItem item)
{
Job newJob = new Job();
newJob.Item = item;
m_JobQueue.Add(newJob);
}
public IList<Job> GetJobsFromQueue(int quantity, ICollection<Job> ignoreList)
{
return (from job in JobQueue
select job).Skip(ignoreList.Count).Take(quantity).ToList();
}
public void CreateJobQueue(ICollection<SelectedItem> jobData)
{
for (long daysCount = 100; daysCount >= 1; daysCount--)
{
SelectedItem item = GetRandomItem(jobData.ToList()); //This just returns a random item from the specified jobData.
item.ItemDate = DateTime.Today.AddDays((daysCount - 1));
CreateJob(item);
}
}
}
Now the issue is when I call the CreateJobQueue() from the main routine, the ItemDate is correctly inserted as in the future depending upon the daysCount value and populates the JobQueue perfectly.
But when try to retrieve a set of Jobs by calling in GetJobsFromQueue() method, the ItemDate values in the whole of JobQueue is messed up (i.e. different to what has been inserted).
Can anyone have a clue?

Categories

Resources