When nesting properties that implement INotifyPropertyChanged must the parent object propagate changes?
Asked Answered
U

4

23

this question is going to show my lack of understanding of the expected behavior when implementing/using INotifyPropertyChanged:

The question is - for binding to work as expected, when you have a class which itself implements INotifyPropertyChanged, that has nested properties of type INotifyPropertyChanged are you expected to internally subscribe to change notification for these properties and then propagate the notifications? Or is the binding infrastructure expected to have the smarts to make this unnecessary?

For example (note this code is not complete - just meant to illustrate the question):

   public class Address : INotifyPropertyChanged
    {
       string m_street
       string m_city;

       public string Street
       {
          get { return m_street; }
          set
          {
             m_street = value;
             NotifyPropertyChanged(new PropertyChangedEventArgs("Street"));
          }
       }

       public string City
       {
          get { return m_city; }
          set 
          {
             m_city = value;
             NotifyPropertyChanged(new PropertyChangedEventArgs("City"));
          }
       }

    public class Person : INotifyPropertyChanged
    {
       Address m_address;

       public Address
       {
          get { return m_address = value; }
          set
          {
             m_address = value;
             NotifyPropertyChanged(new PropertyChangedEventArgs("Address"));
          }
       }
    }

So, in this example we've got a nested Address object in a Person object. Both of which implement INotifyPropertyChanged so that alteration of their properties will result in transmission of property change notifications to subscribers.

But let's say using binding someone is subscribing to change notification on a Person object, and is 'listening' for changes to the Address property. They will receive notifications if the Address property itself changes (a different Address object is assigned) but WILL NOT receive notifications if the data contained by the nested address object (the city, or street) are changed.

This leads to the question - is the binding infrastructure expected to handle this, or should I within my implementation of Person be subscribing to change notifications on the address object and then propagating them as changes to "Address"?

If you get to this point, thanks for just taking the time in reading this long winded question?

Unbuild answered 26/8, 2009 at 17:46 Comment(3)
I found this question after googling around. To me it seems like you have to manually subscribe to childrens PropertyChanged event and bubble it to work in WPF bindings.Ferdinand
loraderon, I'm pretty sure that's not the case - at least in my tests, that has proven to be the case. And, there is no information (that I've found) to state otherwise. Do you have any links to any information you can provide on this? Thanks. PhilUnbuild
I don't have any links either. In my current project I had to bubble PropertyChanged event to make it work. I'm a newbie at WPF and MVVM so it could be just something special with my project.Ferdinand
D
3

One of the simplest ways to do it is to add an event handler to Person which will handle notification events from m_address object:

public class Person : INotifyPropertyChanged
{
   Address m_address;

   public Address
   {
      get { return m_address = value; }
      set
      {
         m_address = value;
         NotifyPropertyChanged(new PropertyChangedEventArgs("Address"));
         m_address.PropertyChanged += new PropertyChangedEventHandler( AddressPropertyChanged );
      }
   }
   void  AddressPropertyChanged( object sender, PropertyChangedEventArgs e )
   {
       NotifyPropertyChanged(new PropertyChangedEventArgs("Address"))
   }
}
Dodecagon answered 1/8, 2010 at 22:22 Comment(3)
tkola, I know how to implement property change notification for children. The question is, is this required for data binding to work correctly. From what I've seen, the answer appears to be No - you do not need to perform change notification when child object change: it is effectively already handled by the binding infrastructure (at least for WPF)Unbuild
You might want to unsubscribe from the old m_address's PropertyChanged event before setting m_address to a new value in Address set.Stenograph
What if Address is a DependencyProperty?Dosage
P
1

You answered this question when you said

...say using binding someone is subscribing to change notification on a Person object,

That someone is subscribing to Person and has no way to know if Address has changed. So you will have to handle this situation on your own (which is quite easy to implement).

Phenix answered 26/8, 2009 at 18:26 Comment(4)
Is that really the case? For example in WPF, I could do this <StackPanel DataContext="{Binding thePerson}" Orientation="Vertical"> <StackPanel Orientation="Horizontal"> <Label Content="Street"/> <TextBox Text={Binding Address.Street}/> </StackPanel> <StackPanel Orientation="Horizontal"> <Label Content="City"/> <TextBox Text={Binding Address.City}/> </StackPanel> </StackPanel> Here, (if I'm correct!), the binding infrastructure would make sure that the city and street textboxes are updated if either the Address property changes or the Street/City changes.Unbuild
Sorry that xaml didn't come out too well in the comment. Anyway, what I'm trying to say is that it could be the requirement of the caller (the entity using the Person object) to register for change notifications on both the person object, and any nested property objects which the caller uses. I'm not saying that this is the case!... That's why I ask the original question because I believe it is possible for two designs (either push responsibility onto the user or onto the implementer). I've tried looking at MS documentation but haven't found anything definitive. Cheers!Unbuild
I am sorry, assumed that you are using Winfomrs. I don't have much knowledge of WPF, but, my guess is that in WPF too it will work exactly the same way. WPF does have concept of events that bubble up and you will probably have to utilize that fact. Look at this article, it should explain you to create custom routed events msdn.microsoft.com/en-us/library/ms742806.aspxPhenix
Thanks PK, I have used routed events and dependency properties but they don't really apply to what I'm using. I'm using the MVVM approach for WPF applications (if you do some WPF, highly recommend looking into it - it's kind of a specific variant of MVP for WPF, but very powerful because of the data driven UI of WPF). With the MVVM approach, there's two alternatives for change notification - WPF dependency properties or INotifyPropertyChanged. It's generally better to use Inotify since it's applicable to any class (no inheritance requirements). Thanks!Unbuild
L
1

If you want to child objects to see as if they are part of a their parent directly you need to do the bubbling yourself.

For your example, you would be binding to 'Address.Street' in your view, so you need to bubble a notifypropertychanged containing that string.

I wrote an easy helper to do this. You just call BubblePropertyChanged(x => x.BestFriend) in your parent view model constructor. n.b. there is an assumption you have a method called NotifyPropertyChanged in your parent, but you can adapt that to suit.

        /// <summary>
    /// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping
    /// the naming hierarchy in place.
    /// This is useful for nested view models. 
    /// </summary>
    /// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param>
    /// <returns></returns>
    public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> property)
    {
        // This step is relatively expensive but only called once during setup.
        MemberExpression body = (MemberExpression)property.Body;
        var prefix = body.Member.Name + ".";

        INotifyPropertyChanged child = property.Compile().Invoke();

        PropertyChangedEventHandler handler = (sender, e) =>
        {
            this.NotifyPropertyChanged(prefix + e.PropertyName);
        };

        child.PropertyChanged += handler;

        return Disposable.Create(() => { child.PropertyChanged -= handler; });
    }
Laconic answered 21/8, 2012 at 17:37 Comment(0)
T
0

An old question, nevertheless...

My original approach was to attach child property changed to the parent. This has an advantage, consuming the event of the parent is easy. Just need to subscribe to the parent.

public class NotifyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    readonly Dictionary<string, AttachedNotifyHandler> attachedHandlers = new Dictionary<string, AttachedNotifyHandler>();

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void AttachPropertyChanged(INotifyPropertyChanged notifyPropertyChanged,
        [CallerMemberName] string propertyName = null)
    {
        if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
        // ReSharper disable once ExplicitCallerInfoArgument
        DetachCurrentPropertyChanged(propertyName);
        if (notifyPropertyChanged != null)
        {
            attachedHandlers.Add(propertyName, new AttachedNotifyHandler(propertyName, this, notifyPropertyChanged));
        }
    }

    protected void DetachCurrentPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
        AttachedNotifyHandler handler;
        if (attachedHandlers.TryGetValue(propertyName, out handler))
        {
            handler.Dispose();
            attachedHandlers.Remove(propertyName);
        }
    }

    sealed class AttachedNotifyHandler : IDisposable
    {
        readonly string propertyName;
        readonly NotifyChangedBase currentObject;
        readonly INotifyPropertyChanged attachedObject;

        public AttachedNotifyHandler(
            [NotNull] string propertyName,
            [NotNull] NotifyChangedBase currentObject,
            [NotNull] INotifyPropertyChanged attachedObject)
        {
            if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
            if (currentObject == null) throw new ArgumentNullException(nameof(currentObject));
            if (attachedObject == null) throw new ArgumentNullException(nameof(attachedObject));
            this.propertyName = propertyName;
            this.currentObject = currentObject;
            this.attachedObject = attachedObject;

            attachedObject.PropertyChanged += TrackedObjectOnPropertyChanged;
        }

        public void Dispose()
        {
            attachedObject.PropertyChanged -= TrackedObjectOnPropertyChanged;
        }

        void TrackedObjectOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            currentObject.OnPropertyChanged(propertyName);
        }
    }
}

The usage is simple:

public class Foo : NotifyChangedBase
{
    Bar bar;

    public Bar Bar
    {
        get { return bar; }
        set
        {
            if (Equals(value, bar)) return;
            bar = value;
            AttachPropertyChanged(bar);
            OnPropertyChanged();
        }
    }
}

public class Bar : NotifyChangedBase
{
    string prop;

    public string Prop
    {
        get { return prop; }
        set
        {
            if (value == prop) return;
            prop = value;
            OnPropertyChanged();
        }
    }
}

However, this approach is not very flexible and there is no control over it, at least without additional complex engineering. If the subscribing system has the flexibility to traverse nested data structures, it's applicability is limited to 1st level children.

While the caveats may be acceptable, depending on usage, I have since moved away from this approach, as it's never certain how the data structure is going to eventually be used. Currently preferring solutions such as this one:

https://github.com/buunguyen/notify

That way even complex data structures are simple and predictable, it is under subscriber control how to subscribe and how to react, it plays well with capabilities of binding engines.

Thier answered 29/4, 2016 at 9:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.