Когда свойства вложенности, которые реализуют INotifyPropertyChanged, должны быть изменены родительский объект propogate?
этот вопрос покажет мое отсутствие понимания ожидаемого поведения при реализации/использовании INotifyPropertyChanged:
Вопрос: для привязки к работе, как и ожидалось, когда у вас есть класс, который сам реализует INotifyPropertyChanged, который имеет вложенные свойства типа INotifyPropertyChanged, вы ожидаете, что будете внутренне подписываться на уведомление об изменении этих свойств, а затем распространять уведомления? Или есть инфраструктура привязки, которая, как ожидается, имеет умственные способности, чтобы сделать это ненужным?
Например (обратите внимание, что этот код не является полным - просто для иллюстрации вопроса):
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"));
}
}
}
Итак, в этом примере у нас есть вложенный объект Address в объекте Person. Оба из них реализуют INotifyPropertyChanged, так что изменение их свойств приведет к передаче уведомлений об изменении свойств подписчикам.
Но скажем, используя привязку, кто-то подписывается на уведомление об изменении объекта Person, и "прослушивает" изменения свойства "Адрес". Они получат уведомления, если само свойство "Адреса" будет изменено (другой объект адреса назначен), но НЕ будет получать уведомления, если данные, содержащиеся в вложенном адресном объекте (городе или улице), будут изменены.
Это приводит к вопросу - должна ли инфраструктура привязки обрабатываться, или я должен в рамках моей реализации Person подписываться на уведомления об изменении на адресном объекте, а затем распространять их как изменения на "Адрес"?
Если вы доберетесь до этого момента, спасибо, просто потратив время на чтение этого долгого вопроса?
Комментарий очень оценен!
Фил
Ответы
Ответ 1
Один из простейших способов сделать это - добавить обработчик событий к Person, который будет обрабатывать события уведомлений из объекта m_address:
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"))
}
}
Ответ 2
Вы ответили на этот вопрос, когда сказали
... скажем, используя подписка на уведомление об изменении объект Person,
То, что кто-то подписывается на Лицо и не имеет способа узнать, изменился ли адрес.
Таким образом, вам придется самостоятельно справляться с этой ситуацией (что довольно легко реализовать).
Ответ 3
Если вы хотите, чтобы дочерние объекты отображались так, как будто они являются частью их родителя, вам нужно самому делать пузырьки.
В вашем примере вы бы привязывались к "Address.Street" в своем представлении, поэтому вам нужно создать пузырь из notifypropertychanged, содержащий эту строку.
Я написал легкий помощник, чтобы сделать это. Вы просто вызываете BubblePropertyChanged (x = > x.BestFriend) в конструкторе модели родительского представления. нотабене есть предположение, что у вас есть метод, называемый NotifyPropertyChanged в вашем родителе, но вы можете адаптировать его для соответствия.
/// <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; });
}
Ответ 4
Старый вопрос, тем не менее...
Мой первоначальный подход состоял в том, чтобы привязать дочернее свойство к родительскому. Это имеет преимущество, так как легко переносить событие родителя. Просто нужно подписаться на родителя.
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);
}
}
}
Использование прост:
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();
}
}
}
Однако этот подход не очень гибкий, и нет контроля над ним, по крайней мере, без дополнительной сложной инженерии. Если система подписки имеет гибкость для пересечения вложенных структур данных, ее применимость ограничена детьми первого уровня.
В то время как оговорки могут быть приемлемыми, в зависимости от использования, я с тех пор отошел от этого подхода, так как он никогда не знает, как будет использоваться структура данных. В настоящее время предпочитают такие решения, как этот:
https://github.com/buunguyen/notify
Таким образом, даже сложные структуры данных просты и предсказуемы, под управлением абонента, как подписываться и как реагировать, он хорошо работает с возможностями механизмов привязки.