.net - onpropertychanged - propertychanged winforms
Al anidar las propiedades que implementan INotifyPropertyChanged, ¿debe el objeto principal propagar cambios? (4)
esta pregunta mostrará mi falta de comprensión del comportamiento esperado al implementar / usar INotifyPropertyChanged:
La pregunta es: ¿para que el enlace funcione como se espera, cuando tiene una clase que implementa INotifyPropertyChanged, que tiene propiedades anidadas de tipo INotifyPropertyChanged, se espera que se suscriba internamente para cambiar la notificación de estas propiedades y luego propagar las notificaciones? ¿O se espera que la infraestructura de enlace tenga la inteligencia para hacer esto innecesario?
Por ejemplo (tenga en cuenta que este código no está completo; solo pretende ilustrar la pregunta):
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"));
}
}
}
Entonces, en este ejemplo tenemos un objeto de Dirección anidado en un objeto de Persona. Ambos implementan INotifyPropertyChanged para que la modificación de sus propiedades dé lugar a la transmisión de notificaciones de cambio de propiedad a los suscriptores.
Pero digamos que usar el enlace de alguien se está suscribiendo para cambiar la notificación en un objeto Person, y está "escuchando" los cambios en la propiedad de Dirección. Recibirán notificaciones si la propiedad de la dirección cambia (se asigna un objeto de dirección diferente), pero NO recibirá notificaciones si se modifican los datos que contiene el objeto de dirección anidado (la ciudad o la calle).
Esto nos lleva a la pregunta: ¿se espera que la infraestructura de enlace maneje esto o debo, dentro de mi implementación de Person, suscribirme para cambiar las notificaciones en el objeto de dirección y luego propagarlas como cambios a "Dirección"?
Si llegas a este punto, ¿gracias por tomarte el tiempo de leer esta larga pregunta?
Si desea que los objetos secundarios se vean como si fueran parte de un elemento primario directamente, debe hacerlo usted mismo.
Para su ejemplo, en su vista, estaría enlazando a ''Address.Street'', por lo que necesita crear una burbuja de notificación que haya cambiado esa cadena.
Escribí un ayudante fácil para hacer esto. Simplemente llame a BubblePropertyChanged (x => x.BestFriend) en el constructor del modelo de vista principal. nb, existe la suposición de que tienes un método llamado NotifyPropertyChanged en tu padre, pero puedes adaptarlo para adaptarlo.
/// <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; });
}
Una de las formas más simples de hacerlo es agregar un controlador de eventos a la Persona que manejará los eventos de notificación desde el objeto 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"))
}
}
Una vieja pregunta, sin embargo ...
Mi enfoque original era adjuntar la propiedad secundaria cambiada al padre. Esto tiene una ventaja, ya que consumir el evento del padre es fácil. Solo necesitas suscribirte al padre.
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);
}
}
}
El uso es 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();
}
}
}
Sin embargo, este enfoque no es muy flexible y no hay control sobre él, al menos sin ingeniería compleja adicional. Si el sistema de suscripción tiene la flexibilidad de atravesar estructuras de datos anidadas, su aplicabilidad se limita a los niños de 1er nivel.
Si bien las advertencias pueden ser aceptables, según el uso, desde entonces me he alejado de este enfoque, ya que nunca es seguro cómo se utilizará la estructura de datos. Actualmente preferimos soluciones como esta:
https://github.com/buunguyen/notify
De esa manera, incluso las estructuras de datos complejas son simples y predecibles, está bajo el control del suscriptor cómo suscribirse y cómo reaccionar, juega bien con las capacidades de los motores de enlace.
Usted respondió esta pregunta cuando dijo
... digamos que usar el enlace de alguien se está suscribiendo para cambiar la notificación en un objeto Person,
Que alguien se suscriba a Persona y no tiene forma de saber si la Dirección ha cambiado. Por lo tanto, tendrá que manejar esta situación por su cuenta (lo cual es bastante fácil de implementar).