I'm having trouble with a situation that I know must be pretty common, so I'm hoping the solution is simple. I have an object that contains a List<> of objects. It also has some properties that reflect aggregate data on the objects in the List<> (actually a BindingList<> so I can bind to it). On my form, I have a DataGridView for the List, and some other fields for the aggregate data. I can't figure out how to trigger a refresh of the aggregate data when values in the DataGridView get changed.
I have tried raising a PropertyChanged event when the properties of the objects in the List are changed, but that doesn't seem to refresh the display of the aggregate data. If I access an aggregate property (eg, display it in a messagebox), the textbox on the main form is refreshed.
Here's some simplified code to illustrate what I'm trying to do:
namespace WindowsFormsApplication1 {
public class Person {
public int Age {
get;
set;
}
public String Name {
get;
set;
}
}
public class Roster : INotifyPropertyChanged {
public BindingList<Person> People {
get;
set;
}
public Roster () {
People = new BindingList<Person>();
}
private int totalage;
public int TotalAge {
get {
calcAges();
return totalage;
}
set {
totalage = value;
NotifyPropertyChanged("TotalAge");
}
}
private void calcAges () {
int total = 0;
foreach ( Person p in People ) {
total += p.Age;
}
TotalAge = total;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged ( String info ) {
if ( PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
}
The calcAges method and the TotalAge property look very suspicious.
First, TotalAge should be read-only. If you allow it to be public and writable, what is the logic for changing the components that make up the age?
Second, every time you get the value, you are firing the PropertyChanged event, which is not good.
Your Roster class should look like this:
public class Roster : INotifyPropertyChanged {
public Roster ()
{
// Set the binding list, this triggers the appropriate
// event binding which would be gotten if the BindingList
// was set on assignment.
People = new BindingList<Person>();
}
// The list of people.
BindingList<Person> people = null;
public BindingList<Person> People
{
get
{
return people;
}
set
{
// If there is a list, then remove the delegate.
if (people != null)
{
// Remove the delegate.
people.ListChanged -= OnListChanged;
}
/* Perform error check here */
people = value;
// Bind to the ListChangedEvent.
// Use lambda syntax if LINQ is available.
people.ListChanged += OnListChanged;
// Technically, the People property changed, so that
// property changed event should be fired.
NotifyPropertyChanged("People");
// Calculate the total age now, since the
// whole list was reassigned.
CalculateTotalAge();
}
}
private void OnListChanged(object sender, ListChangedEventArgs e)
{
// Just calculate the total age.
CalculateTotalAge();
}
private void CalculateTotalAge()
{
// Store the old total age.
int oldTotalAge = totalage;
// If you can use LINQ, change this to:
// totalage = people.Sum(p => p.Age);
// Set the total age to 0.
totalage = 0;
// Sum.
foreach (Person p in People) {
totalage += p.Age;
}
// If the total age has changed, then fire the event.
if (totalage != oldTotalAge)
{
// Fire the property notify changed event.
NotifyPropertyChanged("TotalAge");
}
}
private int totalage = 0;
public int TotalAge
{
get
{
return totalage;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged ( String info ) {
if ( PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Now, when the properties in the list items are changed, the parent object will fire the property changed event, and anything bound to it should change as well.
I believe you may be looking for something like this
ITypedList
Also a Google Search of ITypedList leads you to a few nice blogs on how to implement.
When I use an ORM I typically have to do a few of these for nice datagrid binding and presentation.
Related
I have the following classes:
public class child
{
public string product { get; set; }
public decimal amount { get; set; }
public decimal price { get; set; }
public decimal total { get; set; }
}
public class parent
{
public decimal total { get; set; }
public BindingList<child> childs { get; set; }
}
Now, in a windows form I set the following:
var parent_object = new parent();
numericUpDown1.DataBindings.Add("Value", parent_object , "total", true, DataSourceUpdateMode.OnPropertyChanged);
dataGridView1.DataBindings.Add("DataSource", parent_object , "childs", true, DataSourceUpdateMode.OnPropertyChanged);
Finally (and I have no idea how to do), is that the total property of parent changes automatically when:
The amount or price of some detail of the dataGridView is changed.
A row is added to the dataGridView.
A row is removed from the dataGridView.
Thanks for your help.
Your code so far looks reasonable except that there are a few missing pieces in terms of getting everything hooked up so I'll offer a few suggestions. The first would be to simplify the data binding for the DataGridView where all you need is dataGridView.DataSource = childs. If you did nothing else besides initialize it by overriding MainForm.OnLoad you'd already have a decent-looking view (but it would be missing the two-way interactions).
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
dataGridView.DataSource = childs;
// Add one or more child items to autogenerate columns.
childs.Add(new child
{
product = "GEARWRENCH Pinch Off Pliers",
price = 27.10m,
amount = 1.0m
});
childs.Add(new child
{
product = "AXEMAX Bungee Cords",
price = 25.48m,
amount = 1.0m
});
// Format rows
foreach (DataGridViewColumn column in dataGridView.Columns)
{
switch (column.Name)
{
case nameof(child.product):
column.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
break;
default:
column.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
column.DefaultCellStyle.Format = "F2";
break;
}
}
}
private readonly BindingList<child> childs = new BindingList<child>();
private readonly parent parent_object = new parent();
Bindable Properties
In order to create a bound property that supports two-way communication, you need a way to detect and notify when the properties change. For example, to make the total property bindable in the parent class do this:
public class parent : INotifyPropertyChanged
{
decimal _total = 0;
public decimal total
{
get => _total;
set
{
if (!Equals(_total, value))
{
_total = value;
OnPropertyChanged();
}
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The data binding shown in your code for numericUpDown will now respond to changes of total.
numericUpDown.DataBindings.Add(
nameof(numericUpDown.Value),
parent_object,
nameof(parent_object.total),
false,
DataSourceUpdateMode.OnPropertyChanged);
Responding to Changes Internally
Once you make all of your properties in the child class bindable in the same way by using the example above, consider taking the approach of handling certain changes internally in which case you would suppress the firing of the property change notification.
public class child : INotifyPropertyChanged
{
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
switch (propertyName)
{
case nameof(price):
case nameof(amount):
// Perform an internal calculation.
recalcTotal();
break;
default:
// Notify subscribers of important changes like product and total.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
break;
}
}
private void recalcTotal()
{
total = price * amount;
}
...
}
Connecting the total property in the parent class.
The only thing missing now is having a way to tell the parent_object that a new global total for all the rows is needed. The good news is that the bindings are already in place from the previous steps. To detect any change to the DGV whether a new row is added or a a total is edited, subscribe to the ListChanged event of the childs collection by making this the first line in the OnLoad (before adding any items).
childs.ListChanged += parent_object.onChildrenChanged;
This is a method that will need to be implemented in the parent_object class. Something like this:
internal void onChildrenChanged(object sender, ListChangedEventArgs e)
{
var tmpTotal = 0m;
foreach (var child in (IList<child>)sender)
{
tmpTotal += child.total;
}
total = tmpTotal;
}
READY TO TEST
If you implement these steps, you'll have a fully-functional linked view where you can add, remove, and modify child records.
Hope this gives some overall insight on how all the puzzle pieces fit together.
I have a array property, in which I want to notify whenever any elements of that array gets changed.
private double[] _OffsetAngles = new double[3];
public double[] OffsetAngles
{
get { return _OffsetAngles; }
set
{
_OffsetAngles = value;
NotifyPropertyChanged();
}
}
if any of the elements of OffsetAngles gets changed, I want to get the notification.
i.e. if I set OffsetAngles[1] = 20; //Trigger should happen.
if I set OffsetAngles[0] = 40; //Trigger should happen again.
Imagine that you are not using double but some class. And then that the filed of that class has changed. Should the array raise property changed? Surely not. So there are multiple solutions that you may consider:
use ObservableCollection and its SetItem method
use ObservableCollection and instead of assigning value remove and insert the value
instead of double use some class that implements INotifyPropertyChanged and when the dobule changes raise this event, it should be right approach if the purpose is data binding
recreate the Array each time (cumbersome and inefficient but still works)
So as others have mentioned, in your case you fire the NotifyPropertyChanged() when the array itself is changed, not any element of the array.
If you want the elements to be able to fire the event you would have to implement a class like:
public class NotifyingData<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private T _Data;
public T Data
{
get { return _Data; }
set { _Data = value; NotifyPropertyChanged(); }
}
}
and then populate your array with that class:
_OffsetAngles[0] = new NotifyingData<double> { Data = 10 };
I don't have access to VS right now, so there might be some errors, but this should be the right concept for you.
This example shows how to create and bind to a collection that derives from the ObservableCollection class, which is a collection class that provides notifications when items get added or removed.
public class NameList : ObservableCollection<PersonName>
{
public NameList() : base()
{
Add(new PersonName("Willa", "Cather"));
Add(new PersonName("Isak", "Dinesen"));
Add(new PersonName("Victor", "Hugo"));
Add(new PersonName("Jules", "Verne"));
}
}
public class PersonName
{
private string firstName;
private string lastName;
public PersonName(string first, string last)
{
this.firstName = first;
this.lastName = last;
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
The objects in your collection must satisfy the requirements described in the Binding Sources Overview. In particular, if you are using OneWay or TwoWay (for example, you want your UI to update when the source properties change dynamically), you must implement a suitable property changed notification mechanism such as the INotifyPropertyChanged interface.
Ref: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-create-and-bind-to-an-observablecollection
I had the same issue a while ago. I had to update a DataTable whenever the data changed and that's how I solved it on my program :
public ObservableCollection<KeyStroke> keyList = new ObservableCollection<KeyStroke>();
public class KeyStroke : INotifyPropertyChanged
{
// KeyStroke class storing data about each key and how many types it received
private int id;
private int numPress;
public KeyStroke(int id, int numPress)
{
Id = id;
NumPress = numPress;
}
public int Id
{
get => id;
set
{
id = value;
NotifyPropertyChanged("Id");
}
}
public int NumPress
{
get { return this.numPress; }
set
{
this.numPress = value;
NotifyPropertyChanged("NumPress");
}
}
public event PropertyChangedEventHandler PropertyChanged; //This handle the propertyChanged
private void NotifyPropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); //This is the WPF code for the DataGrid but you can replace it by whatever you need
}
}
This should help you. You can also put conditions inside the getter/setters of the properties but I think it's not really pretty
In my model class, I have several lists and other properties. One of these properties is called CurrentIteration and constantly gets updated. When this gets updated, I want other properties to update themselves to the element of a corresponding list that is the index of CurrentIteration. I thought all's I needed to include was an OnPropertyChanged event for the properties I want to update in the setter of CurrentIteration. However, they don't seem to be getting called.
public class VehicleModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<double> _nowTime = new List<double>();
public List<double> NowTime
{
get { return this._nowTime; }
set { this._nowTime = value; OnPropertyChanged("Nowtime"); }
}
private List<double> _VehLat = new List<double>();
public List<double> VehLat
{
get { return this._VehLat; }
set { this._VehLat = value; OnPropertyChanged("VehLat"); }
}
private List<double> _VehLong = new List<double>();
public List<double> VehLong
{
get { return _VehLong; }
set { _VehLong = value; OnPropertyChanged("VehLong"); }
}
//non-list properties
private int _currentIteration;
public int CurrentIteration //used to hold current index of the list of data fields
{
get { return _currentIteration; }
set
{
_currentIteration = value;
OnPropertyChanged("CurrentIteration");
OnPropertyChanged("CurrentVehLat");
OnPropertyChanged("CurrentVehLong");
}
}
private double _currentVehLat;
public double CurrentVehLat
{
get { return _currentVehLat; }
set { _currentVehLat = VehLat[CurrentIteration]; OnPropertyChanged("CurrentVehLat"); }
}
private double _currentVehLong;
public double CurrentVehLong
{
get { return _currentVehLong; }
set { _currentVehLong = VehLong[CurrentIteration]; OnPropertyChanged("CurrentVehLong"); }
}
public void SetData(int i)
{
CurrentIteration = i;
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
CurrentIteration DOES get updated properly, but the rest do not. The setters get skipped over completely. I'm almost positive it's something simple and my understanding of the setters in this case is wrong, but I'm not sure what it is exactly.
Edit: Here's an example of one of the bindings in XAML:
Text="{Binding Path=CurrentVehLong,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Raising the property change notification just says "These properties have changed, re-query their values". Meaning it calls the get accessor method.
It looks like you are expecting it to call the set method, which is not going to happen because you are not setting the value.
Try removing the backing property and just access the array value directly, like this :
public double CurrentVehLong
{
get { return VehLong[CurrentIteration];; }
set
{
VehLong[CurrentIteration] = value;
OnPropertyChanged("CurrentVehLong");
}
}
Now when you call OnPropertyChanged("CurrentVehLong"), it will re-query the get accessor for this property, and update the value based on the CurrentIteration. I also altered the set method so it would make more sense, and you'd be able to use it to set the value if that's what you want to do elsewhere.
I am writing windows phone 8.1 universal application and main applicaiton control is Pivot with few pivot items. In the pivot items are ListViews containing TestItems. I want to filter items on one list by IsRead property. Is it possible to just filter main collection without keeping 2 collections? CollectionViewSource does not support filtering a sorting on universal apps, if I know. But keeping (and synchronizing on changes) two collections doesn't look like good idea.
EDIT:
I have used ObservableCollection because list of items may be updated on the background. Probably it was not clear from original question.
class TestItem : ModelBase
{
private bool isRead;
public bool IsRead
{
get { return isRead; }
set
{
isRead = value;
NotifyPropertyChanged();
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
}
}
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
You can use Linq;
In your case:
using System.Linq;
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
//public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
public IEnumerable<TestItem> ItemsRead
{
get
{
IEnumerable<TestItem> itemsRead = from item in Items
where item.IsRead
select item;
return itemsRead;
}
}
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
Please, check syntax, it can contain some mistakes.
You can manipulate with the first collection, the second collection will be automatically updated.
You can define a CollectionViewSource in your XAML:
<Grid.Resources>
<CollectionViewSource x:Name="MyCollectionViewSource"/>
</Grid.Resources>
And then set it's source like this:
//Global variable
MainViewModel vm;
//Constructor
public MyPage(){
//Other code
vm = new MainViewModel();
vm.Items.CollectionChanged += Items_CollectionChanged;
UpdateViewSource();
}
private void Items_CollectionChanged(object sender, CollectionChangedEventArgs e){
UpdateViewSource();
}
private void UpdateViewSource(){
MyCollectionViewSource.Source = vm.Items.Where(x => x.IsRead);
}
I haven't tested this code.
You need only one ObservableCollection containing the initial objects and another property (let's say ItemsFiltered) with a get method returning the results after filtering. In the constructor you can subscribe to the CollectionChanged event of the observable collection to raise the OnPropertyChanged event for the ItemsFiltered property. You raise the same event when the filter state is changed. This is a simple example:
public MainViewModel()
{
_initialItems.CollectionChanged += (sender, e) => OnPropertyChanged("Items");
}
private ObservableCollection<TestItem> _initialItems = new ObservableCollection<TestItem>();
public List<TestItem> Items
{
get
{
if (IsReadFilter)
{
return _initialItems.Where(i => i.IsRead).ToList();
}
return _initialItems;
}
}
private bool _isReadFilter;
public bool IsReadFilter
{
get { return _isReadFilter; }
set
{
if (_isReadFilter != value)
{
_isReadFilter = value;
OnPropertyChanged("IsReadFilter");
OnPropertyChanged("Items");
}
}
}
Basically, the idea is that every time IsReadFilter value is changed, the UI gets notified that the Items property is changed and calls its get method to get the new value and update. Items are also updated every time the observable collection is changed from other places.
If I had an Observable collection like so :
public ObservableCollection<SpecialPriceRow> SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
SpecialPriceRow class :
public class SpecialPriceRow : INotifyPropertyChanged
{
public enum ChangeStatus
{
Original,
Added,
ToDelete,
Edited
}
public ChangeStatus Status { get; set; }
public string PartNo { get; set; }
private decimal _price;
public decimal Price
{
get
{
return _price;
}
set
{
if (value != _price)
{
_price = value;
Status = ChangeStatus.Edited;
OnPropertyChanged("Status");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Would it be possible for me to bind a Label in the XAML to the count of objects that are say ... Added? So I could get something like this :
Where green is the count of "Added" objects within the collection. How would I go about doing something like this?
I've written up a ViewModel which will perform the desired functionality you are looking for.
class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<SpecialPriceRow> _SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
private int _Original = 0;
private int _Added = 0;
private int _ToDelete = 0;
private int _Edited = 0;
public VM()
{
PropertyChanged = new PropertyChangedEventHandler(VM_PropertyChanged);
//The following lines in the constructor just initialize the SpecialPriceRows.
//The important thing to take away from this is setting the PropertyChangedEventHandler to point to the UpdateStatuses() function.
for (int i = 0; i < 12; i++)
{
SpecialPriceRow s = new SpecialPriceRow();
s.PropertyChanged += new PropertyChangedEventHandler(SpecialPriceRow_PropertyChanged);
SpecialPriceRows.Add(s);
}
for (int j = 0; j < 12; j+=2)
SpecialPriceRows[j].Price = 20;
}
private void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private void SpecialPriceRow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Status")
UpdateStatuses();
}
public ObservableCollection<SpecialPriceRow> SpecialPriceRows
{
get
{
return _SpecialPriceRows;
}
}
private void UpdateStatuses()
{
int original = 0, added = 0, todelete = 0, edited = 0;
foreach (SpecialPriceRow SPR in SpecialPriceRows)
{
switch (SPR.Status)
{
case SpecialPriceRow.ChangeStatus.Original:
original++;
break;
case SpecialPriceRow.ChangeStatus.Added:
added++;
break;
case SpecialPriceRow.ChangeStatus.ToDelete:
todelete++;
break;
case SpecialPriceRow.ChangeStatus.Edited:
edited++;
break;
default:
break;
}
}
Original = original;
Added = added;
ToDelete = todelete;
Edited = edited;
}
public int Original
{
get
{
return _Original;
}
set
{
_Original = value;
PropertyChanged(this, new PropertyChangedEventArgs("Original"));
}
}
public int Added
{
get
{
return _Added;
}
set
{
_Added = value;
PropertyChanged(this, new PropertyChangedEventArgs("Added"));
}
}
public int ToDelete
{
get
{
return _ToDelete;
}
set
{
_ToDelete = value;
PropertyChanged(this, new PropertyChangedEventArgs("ToDelete"));
}
}
public int Edited
{
get
{
return _Edited;
}
set
{
_Edited = value;
PropertyChanged(this, new PropertyChangedEventArgs("Edited"));
}
}
}
Take note of the comments in the constructor. You need to point the PropertyChanged event of each SpecialPriceRow to the UpdateStatuses function in order for this to work properly.
Now all you need to do is bind your labels to the appropriate properties in the ViewModel.
If your SpecialPriceRows list becomes very large, you may want to consider calculating the status counts a bit differently. Currently, it is iterating through the entire list every time one instance is updated. For this to perform better, you may want to keep the old value of the status in the SpecialPriceRow class and every time an update occurs, increment the new status count and decrement the old one.
I'm not aware of any builtin functionality to do this. I would create a custom property in your data context class that does the counting and bind to this.
Something like this:
public int AddedCount
{
get
{
return SpecialPriceRows.Where(r => r.Status == ChangeStatus.Added).Count();
}
}
Then whenever an item is changed or added you need to explicitly raise the property changed for this:
public void AddItem()
{
...
OnPropertyChanged("AddedCount");
}
Then you only need to bind in your XAML code like:
<TextBlock Text="{Binding AddedCount}" />
You may need to subscribe to the change events in your collection to know when an item changes.
Alternative:
You can also create a ListCollectionView with a specific filter and bind to its Count property:
AddedItems = new ListCollectionView(TestItems);
AddedItems.Filter = r => ((SpecialPriceRow)r).Status == ChangeStatus.Added;
In your XAML you would then bind to the Count property of this:
<TextBlock Text="{Binding AddedItems.Count}" />
This has the advantage that it will automatically keep track of added and removed items in the collection and update itself. You have to refresh it manually though when the property of an item changes which affects the filter.