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.
CodePudding user response:
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 onl oad(EventArgs e)
{
base.OnLoad(e);
dataGridView.DataSource = childs;
// Add one or more child items to autogenerate rows.
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.