Search code examples
c#mvvmuwptemplate10

RaisePropertyChanged does not notify parent


We have a parent class which has a collection of children. Both classes support change notification using Template10. Change notification works within each class (ie when we update a property within the same class) as below, but we are unable to trigger change notification on the parent from the child.

public class ParentViewModel : ViewModelBase
{
    public string ParentString { get }
    public decimal? Net { get { return Total / (1 + TaxRate / 100); } }        
    public decimal? Tax { get { return Total - Net; } }
    decimal? _Total = default(decimal?);
    public decimal? Total
    {
        get
        {
            return _Total;
        }
        set
        {
            Set(ref _Total, value);
            RaisePropertyChanged(nameof(Net));
            RaisePropertyChanged(nameof(Tax));
        }
    }

    public ObservableCollection<ChildViewModel> MyChildren { get; set; }

We find we are able to use RaisePropertyChanged in ParentViewModel to fire

Net { get { return Total / (1 + TaxRate / 100); } }

and

Tax { get { return Total - Net; } }

On ChildViewModel we have ChildString. We want to notify ParentString of changes to ChildString.

public class ChildViewModel : ViewModelBase
{
    ParentViewModel MyParent { get; set; }
    string _ChildString = default(string);
    public string ChildString
    {
        get
        {
            return _ChildString;
        }
        set
        {
            Set(ref _ChildString, value);
            RaisePropertyChanged(nameof(this.MyParent.ParentString));
        }
    }

However ParentString is not updating. How do we force ParentString to update when ChildString updates?


Solution

  • Regardless of nameof argument, when you call ChildViewModel.RaisePropertyChanged, you fire event on child view model.

    nameof operator can't "change" event source. You need a way to fire parent PropertyChanged somehow. The easiest way is to make appropriate method in ParentViewModel:

    public void RaiseParentStringChanged()
    {
        RaisePropertyChanged(nameof(ParentString));
    }
    

    and call it from child:

    public string ChildString
    {
        get
        {
            return _ChildString;
        }
        set
        {
            Set(ref _ChildString, value);
    
            // assuming, that parent is assigned at this point
            MyParent.RaiseParentStringChanged();
        }
    }
    

    But it will be nice to throw away parent from child, if changing of ParentString is the only reason to bring that dependency.

    Since MyChildren is an observable collection, you can subscribe on its CollectionChanged event. When new child is being added, you can subscribe on its PropertyChanged, and watch for ChildString changes to raise appropriate parent event.

    It's something like this:

    // non-related code is omitted
    public class ParentViewModel: ViewModelBase
    {
        private ObservableCollection<ChildViewModel> children;
    
        public string ParentString { get; }
    
        // don't do collection properties on view models
        // as get-set ones, unless you know exactly what are you doing;
        // usually you don't need to set them explicitly from outside
        public ObservableCollection<ChildViewModel> MyChildren
        {
            get
            {
                if (children == null)
                {
                    children = new ObservableCollection<ChildViewModel>();
                    children.CollectionChanged += ChildrenChanged;
                }
                return children;
            }
        }
    
        private void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // assuming, that only two actions ("add" and "remove") are available;
            // if you really need to nadle "reset" and "replace", add appropriate code;
            // note, that "reset" doesn't provide acceess to removed items
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    ((ChildViewModel)e.NewItems[0]).PropertyChanged += ChildPropertyChanged;
                    break;
                case NotifyCollectionChangedAction.Remove:
                    ((ChildViewModel)e.OldItems[0]).PropertyChanged -= ChildPropertyChanged;
                    break;
            }
        }
    
        private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(ChildViewModel.ChildString))
            {
                RaisePropertyChanged(nameof(ParentString));
            }
        }
    }