4

If I have the following layout:


public class A : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public B { get; set; }
}

public class B { public C { get; set; } }
public class C { public D { get; set; } }
public class D { public E { get; set; } }

//... add n classes

public class Z
{
    public int Property
    {
        set
        {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Property"));
        }
    }
}

What is the cleanest way for me to notify A when A.B.C.D.E...Z.Property changes?

When anything inside of A changes, I want it to be flagged as "dirty" so I can tell the system that A needs to be saved.

skaffman
  • 398,947
  • 96
  • 818
  • 769
Michael
  • 3,099
  • 4
  • 31
  • 40

3 Answers3

1

For line of business application that have a common base class I do this as per

Implementing INotifyPropertyChanged - does a better way exist?

with some modifications to check for "bubbling" properties.

Base Class

 public bool HasAlteredState { get; protected set; }

 public event PropertyChangedEventHandler PropertyChanged;

 private void propertyObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.OnPropertyChanged(e.PropertyName);
        }

 protected virtual void RegisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
        {
            propertyObject.PropertyChanged += new PropertyChangedEventHandler(propertyObject_PropertyChanged);
        }

 protected virtual void DeregisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
        {
            propertyObject.PropertyChanged -= propertyObject_PropertyChanged;
        }

  protected virtual void OnPropertyChanged(string propertyName)
    {
        this.HasAlteredState = true;
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        MemberExpression body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }

    protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
    {
         if (EqualityComparer<T>.Default.Equals(field, value)) return false;

        if (field is INotifyPropertyChanged)
        {
            if (field != null) { this.DeregisterSubPropertyForChangeTracking((INotifyPropertyChanged)field); }
        }
        if (value is INotifyPropertyChanged)
        {
            if (value != null) { this.RegisterSubPropertyForChangeTracking((INotifyPropertyChanged)value); }
        }

        field = value;
        OnPropertyChanged(selectorExpression);
        return true;
    }

Sub classes

private IndividualName _name;
public IndividualName PersonName
        {
            get { return _name; }
            set { SetField(ref _name, value, () => PersonName); }
        }

Provides

  1. Simple property change notification
  2. Complex property change notification
  3. Event "bubbling" from INotifyPropertyChanged implementations deeper in the object graph
  4. Compile time checking that your property "name" actually refers to your property. i.e. avoid nasty bugs related to spelling a property name wrong when just using a string.

Performance

There is an associated performance hit to this approach... about 20% slower than just using a string. That said although the metrics and tracing say it's slower I cant actually tell the difference so the hit is worth it re: application maintenance for the kinds of apps I'm involved in developing.

Alternative Implementations

  1. If base class is not an option you could go Extension method route.
  2. For better performance you could have two different SetField methods; the first SetNotifyField would deal with properties which themselves implement INotifyPropertyChanged (as above) and the second SetField would deal with simple properties. i.e. cut out the

    if (field is INotifyPropertyChanged)...

Community
  • 1
  • 1
rism
  • 11,932
  • 16
  • 76
  • 116
1

I was actually working on this exact same problem just recently. My approach was to simply let B, C, D, etc. to manage their own Dirty state, and then modified A's IsDirty property as such:

public bool IsDirty
{
   get
   {
        return _isDirty || B.IsDirty || C.IsDirty /* etc */;
   }
}

To me, this is not only simple, but it makes the most sense. A is dirty if any of it's properties have changed, and B, C, D, etc are all properties of A.

CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
1

I didn't test it, but following one should work. I don't remember why, but I think you cannot handle PropertyChanged events. You should declare your own delegate (VoidHandler).

public delegate void VoidHandler(object sender);

public class B // also C,D,E,...
{
  // A.ItemChanged() will be wired to this SomethingChangedHandler.
  // I heard you are saving. Exclude SomethingChangedHandler from save.
  [field: NonSerialized]
  public VoidHandler SomethingChangedHandler;

  private c;
  public C
  {
    set
    {
      // unwire handler from old instance of C
      if(c != null)
        c.SomethingChangedHandler -= ItemChanged;

      // wire handler to new instance of C
      value.SomethingChangedHandler += ItemChanged;

      c = value;

      // setting c is also change which require notification
      ItemChanged(this);
    }
    get{}
  }

  // notify A about any change in B or in C
  void ItemChanged(object sender)
  {
    if(SomethingChangedHandler != null)
      SomethingChangedHandler(this);
  }
}
jing
  • 1,919
  • 2
  • 20
  • 39