Home > database >  c# Notify parent property from child
c# Notify parent property from child

Time:07-30

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:

  1. The amount or price of some detail of the dataGridView is changed.
  2. A row is added to the dataGridView.
  3. 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).

prelim

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.

functional-binding

Hope this gives some overall insight on how all the puzzle pieces fit together.

  • Related