Bubbling INotifyPropertyChanged и вложенные свойства - PullRequest
3 голосов
/ 04 марта 2011

Если у меня следующий макет:


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"));
        }
    }
}

Какой самый чистый способ уведомить меня об изменении ABCDE..Z.Property?

Когда что-то внутри A изменяется, я хочу, чтобы оно было помечено как «грязное», чтобы я мог сказать системе, что необходимо сохранить A.

Ответы [ 3 ]

1 голос
/ 05 марта 2011

Я не проверял, но следующий должен работать.Я не помню почему, но я думаю, что вы не можете обрабатывать события PropertyChanged.Вы должны объявить свой собственный делегат (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);
  }
}
1 голос
/ 16 октября 2012

Для бизнес-приложений, имеющих общий базовый класс, я делаю это согласно

Реализация INotifyPropertyChanged - существует ли лучший способ?

с некоторыми изменениями для проверки "пузырящихся" свойств.

Базовый класс

 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;
    }

Подклассы

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

Обеспечивает

  1. Простое уведомление об изменении свойства
  2. Сложное уведомление об изменении свойства
  3. Событие «всплывающее» из INotifyPropertyChanged реализации глубже в графе объектов
  4. Время компиляции, проверяющее, что «имя» вашей собственности действительно относится к вашей собственности. т.е. избегать неприятных ошибок, связанных с неправильным написанием имени свойства при использовании только строки.

Производительность

С этим подходом связано снижение производительности ... примерно на 20% медленнее, чем при использовании строки. Тем не менее, хотя метрики и трассировка говорят о том, что он медленнее, я на самом деле не могу понять разницу, поэтому хит того стоит: обслуживание приложений для тех видов приложений, которые я участвую в разработке.

Альтернативные реализации

  1. Если базовый класс не является опцией, вы можете пойти по маршруту метода Extension.
  2. Для лучшей производительности вы можете использовать два разных метода SetField; первый SetNotifyField будет иметь дело со свойствами, которые сами реализуют INotifyPropertyChanged (как указано выше), а второй SetField будет иметь дело с простыми свойствами. то есть вырезать

    if (поле INotifyPropertyChanged) ...

1 голос
/ 04 марта 2011

Я на самом деле работал над этой же проблемой совсем недавно. Мой подход состоял в том, чтобы просто позволить B, C, D и т. Д. Управлять своим собственным состоянием Dirty, а затем изменить свойство A IsDirty следующим образом:

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

Для меня это не только просто, но и имеет смысл. A является грязным, если какое-либо из его свойств изменилось, а B, C, D и т. Д. Являются всеми свойствами A.

...