Is there a way to trigger some kind of `OnPropertyChanged` event for a computed property that uses a property of a "child entity" in a collection?

StackOverflow https://stackoverflow.com/questions/13257888

Question

Is there a way to trigger some kind of OnPropertyChanged event for a computed property that uses a property of a "child entity" in a collection?

A small example:

I have a simple WPF application with a DataGrid showing Customer properties. I am using Entity Framework 5, CodeFirst approach, so I wrote my classes manually with my own INotifyPropertyChanged implementation.

public partial class Customer : INotifyPropertyChanged
{
    private string _firstName;
    public virtual string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
            OnPropertyChanged("FullName");
        }
    }

    private string _lastName;
    public virtual string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged("LastName");
            OnPropertyChanged("FullName");
        }
    }

    public virtual ICollection<Car> Cars { get; set; }
    public virtual ICollection<Invoice> Invoices { get; set; }

    ...
}

Now in that same class I created 3 computed properties:

    public string FullName
    {
        get { return (FirstName + " " + LastName).Trim(); }
    }

    public int TotalCars
    {
        get
        {
            return Cars.Count();
        }
    }

    public int TotalOpenInvoices
    {
        get
        {
            if (Invoices != null)
                return (from i in Invoices
                        where i.PayedInFull == false
                        select i).Count();
            else return 0;
        }
    }

The FullName is automatically updated in the DataGrid because I'm calling OnPropertyChanged("FullName");

I found an example of the INotifyCollectionChanged implementation that I can probably use to auto update the TotalCars when something is added to or removed from the ICollection: http://www.dotnetfunda.com/articles/article886-change-notification-for-objects-and-collections.aspx

But what is the best approach to trigger the OnPropertyChange("TotalOpenInvoices") when a property (PayedInFull) inside the ICollection (Invoices) changes?

Doing something like OnPropertyChanged("Customer.TotalOpenInvoices"); in the Invoice class doesn't seem to do the trick... :)

Was it helpful?

Solution

Do you have control of what goes inside your collections? If you do, you can create a Parent property on your Invoice object and when it is added to the collection, set the parent to your Customer. Then when PaidInFull gets set, run your Customer.OnPropertyChanged("TotalOpenInvoices") or call a method on the Customer object to recalculate your invoices.

Enforcing parent-child relationship in C# and .Net

OTHER TIPS

This assumes that Invoice and Customer both implement IPropertyChanged. Simply change your collection to an ObservableCollection, and watch the CollectionChanged property.

When new Invoices are added, hook up an event handler to the PropertyChanged event of that Invoice. When an item is removed from the collection, remove that event handler.

Then, just call your NotifyPropertyChanged function on your TotalOpenInvoices property.

For example (not completely tested, but it should be close):

Invoice

public class Invoice : INotifyPropertyChanged
    {

        private bool payedInFull = false;
        public bool PayedInFull
        {
            get { return payedInFull; }
            set
            {
                payedInFull = value;
                NotifyPropertyChanged("PayedInFull");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }

Customer

public class Customer : INotifyPropertyChanged
{
    public Customer()
    {
        this.Invoices.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Invoices_CollectionChanged);
    }

    ObservableCollection<Invoice> Invoices 
    {
        get;
        set;
    }

    public int TotalOpenInvoices
    {
        get
        {
            if (Invoices != null)
                return (from i in Invoices
                        where i.PayedInFull == false
                        select i).Count();
            else return 0;
        }
    }


    void Invoices_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        foreach (Invoice invoice in e.NewItems)
        {
            invoice.PropertyChanged += new PropertyChangedEventHandler(invoice_PropertyChanged);
            NotifyPropertyChanged("TotalOpenInvoices");
        }

        foreach (Invoice invoice in e.OldItems)
        {
            invoice.PropertyChanged -= new PropertyChangedEventHandler(invoice_PropertyChanged);
            NotifyPropertyChanged("TotalOpenInvoices");
        }
    }

    void invoice_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "PayedInFull")
            NotifyPropertyChanged("TotalOpenInvoices");
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

Note that each class could be abstracted to a single class that implements INotifyPropertyChanged, such as a ViewModelBase class, but that is not necessarily required for this example.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top