1

I have a view model that has several properties that are databound to several controls.

When I raise PropertyChanged on one of them, the controls unexpectedly all update. I would expect only the one I am raising the event on to update.

For my form, I have this:

    public partial class MainForm : Form
    {
        AmountCalculatorVM amountCalculatorVM;
        public MainForm()
        {
            InitializeComponent();
        }

        private void setBindings()
        {
            textBoxTotalAmount.DataBindings.Add("Text", amountCalculatorVM, "TotalAmount");
            textBoxAverage.DataBindings.Add("Text", amountCalculatorVM, "Average",true, DataSourceUpdateMode.Never,null, "#.00");
            textBoxCount.DataBindings.Add("Text", amountCalculatorVM, "Count");
            listBoxLineAmounts.DataSource = amountCalculatorVM.Amounts;
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            amountCalculatorVM = new AmountCalculatorVM();
            setBindings();
        }

        private void buttonAddAmount_Click(object sender, EventArgs e)
        {
            if (int.TryParse(textBoxLineAmount.Text.Replace(",", ""), out int amount))
            {
                amountCalculatorVM.Amounts.Add(amount);
                textBoxLineAmount.Text = "";
                textBoxLineAmount.Focus();
            }
        }


        private void buttonClear_Click(object sender, EventArgs e)
        {
            textBoxLineAmount.Text = "";
            amountCalculatorVM.Amounts.Clear();
            textBoxLineAmount.Focus();
        }

    }

Then, for my view model, I have this:

    class AmountCalculatorVM : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private readonly AmountList amounts;
        public BindingSource Amounts { get; }
        public int TotalAmount => amounts.TotalAmount;
        public int Count => amounts.Count;
        public decimal Average => amounts.Average;
        public AmountCalculatorVM()
        {
            amounts = new AmountList();
            Amounts = new BindingSource();
            Amounts.DataSource = amounts;
            Amounts.ListChanged += Amounts_ListChanged;
        }

        private void Amounts_ListChanged(object sender, ListChangedEventArgs e)
        {

            //Any one of these will cause all three textboxes to update in the form
            //I would think that with Count and Average commented out, the Count and 
            //Average textboxes would not update.
            OnPropertyChanged("TotalAmount"); 
            //OnPropertyChanged("Count"); 
            //OnPropertyChanged("Average");

            //Using any other word will not
            //OnPropertyChanged("SomeOtherRandomWord");

        }
    }

Here is the AmountList class for reference:

 class AmountList : List<int>
    {
        public int TotalAmount
        {
            get
            {
                int total = 0;
                foreach (int amount in this)
                {
                    total += amount;
                }
                return total;
            }
        }

Now, unexpectedly, all three textboxes update if an item is added to the amounts list, which fires ListChanged, and then in turn, the PropertyChanged event.

It doesn't matter which of the three properties I fire PropertyChanged on, but it won't work if I use a different value - it needs to be either TotalAmount, Count, or Average.

I can't understand this behaviour. I would have expected only the text box bound to TotalAmount to be updated, and not the other two, since nothing seems to be notifying them that an update has occurred.

Any ideas?

Sam Hoff
  • 11
  • 4
  • Er, you seem to be saying that you have an amounts list, and a mechanism for cailvialtobg eg the count or average of all the amounts in the list, and your don't expect/want the average/count to update when you add/remove an amount to/from the list. That seems odd? – Caius Jard Nov 06 '20 at 07:01
  • It's not that I don't want that to happen. It's just that it doesn't seem like it should be happening. I want to understand so I don't inadvertently update controls in the future. – Sam Hoff Nov 06 '20 at 07:30
  • What shouldn't be happening? Pretty sure if I made a mechansim that calced the average to 100 things I would want it to recalc if I added a 101st? – Caius Jard Nov 06 '20 at 07:38
  • Since the PropertyChanged event is raised with "TotalAmount" as the eventArgs parameter, I can't see how the count and average textboxes know to update. This question is about trying to understand the databinding mechanism more that what would be the preferred outcome in this particular case. – Sam Hoff Nov 06 '20 at 07:47
  • You have Controls bound to the three properties you have mentioned. When you change a property value (of a Property that raises `PropertyChanged` events), a `PropertyChanged` event is propagated by the BindingSource, so all bound Controls are updated based on the value of the `Current` object. The *misundestanding* is that you think that is your `Amounts_ListChanged` handler that causes (or should cause) a `PropertyChanged` event: that already happened, your handler allows you to take action when the `ListChanged` event is raised. – Jimi Nov 06 '20 at 14:08
  • I think you are saying that when Property changed is raised, then all controls bound to any of the object's properties are all updated, and it isn't a case where only the property whose name was based in as an EventArg has bound controls updated? That would make sense. I don't understand the bit about the ListChanged handler though - if I don't handle this event and then raise PropertyChanged, then the controls do not update, as they don't know the list and their bound properties have been updated when the list changes. – Sam Hoff Nov 06 '20 at 16:04
  • This is because your implementation of the `INotifyPropertyChanged` pattern is improper. Your properties should raise that event when one of the values changes. See, for example, these classes [here](https://stackoverflow.com/a/51435842/7444103) and [here](https://stackoverflow.com/a/61238462/7444103) and a *manual* implementation [here](https://stackoverflow.com/a/52685685/7444103) (since that class is `static`, so it cannot implement the Interface directly, a makeshift `INotifyPropertyChanged` pattern is used) – Jimi Nov 06 '20 at 16:52
  • The `ListChanged` event is raised when the List itself changes, when you add or remove elements to/from the List, not when members (properties, here) of an object in the List are changed. I.e., changing a Property of an object inside a List of objects doesn't change the List. – Jimi Nov 06 '20 at 16:57
  • Hmmmmm.... Ok, thanks for the input. What would you suggest as a way to notice and notify the UI controls that the Count, Average, and TotalAmount have changed when items are added/removed from the list then? These aren't properties of objects in the list, but properties of the list itself. I am trying to raise PropertyChanged when those properties change, which is when the list updates, which seems in line with your comment. – Sam Hoff Nov 06 '20 at 18:36
  • It depends on how your `AmoutCalculator` and the `AmountList` work together and on the definition of the AmountList class itself. What notifies what here? Do you add elements, e.g., `Amount` objects, to the `AmountList`? How does the `AmountList` notifiy `AmoutCalculator` that its values have changed? It looks like it doesn't, `AmoutCalculator` just exposes those *calculated properties*. -- Since the change is actually generated in `AmountList`, you could make this object raise a `PropertyChanged` notification. `AmoutCalculator` could subscribe to this event and forward the notification. – Jimi Nov 06 '20 at 20:35
  • Anyway, in these cases, you should really post the complete definition of your classes and a description of their interaction functionality. – Jimi Nov 06 '20 at 20:36
  • Thanks @Jimi - I've added the complete code for clarity. To answer your question - AmountList notifies AmountCalculator via handling the ListChanged event. I'm not sure how I could know that the list changed with code in AmountList. – Sam Hoff Nov 06 '20 at 21:23

1 Answers1

0

Why don't you implement the propertychanged like this:

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

You can control now, in the setter, which property fires the event:

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, "Name"); }
}

you know what I mean?

  • I appreciate the reply, but I'm not sure that helps in this case, since there is no property being set. – Sam Hoff Nov 06 '20 at 07:29