2009-08-26 5 views
16

esta pregunta va a mostrar mi falta de entendimiento del comportamiento esperado en la aplicación/uso de INotifyPropertyChanged:Cuando las propiedades de anidamiento que implementan INotifyPropertyChanged deben cambiar la propoción del objeto principal?

La pregunta es - por la unión a trabajar como se esperaba, cuando se tiene una clase que a su vez implementa INotifyPropertyChanged, que ha anidado propiedades del tipo INotifyPropertyChanged ¿se espera que se suscriba internamente para cambiar la notificación de estas propiedades y luego propocionar las notificaciones? ¿O se espera que la infraestructura vinculante tenga la inteligencia necesaria para hacer esto innecesario?

Por ejemplo (nota de este código no es completa - sólo la intención de ilustrar la cuestión):

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

Por lo tanto, en este ejemplo tenemos un objeto Dirección anidada en un objeto Person. Ambos implementan INotifyPropertyChanged para que la alteración de sus propiedades resulte en la transmisión de notificaciones de cambio de propiedad a los suscriptores.

Pero digamos que al usar el enlace, alguien se suscribe para cambiar la notificación en un objeto Persona, y está 'escuchando' los cambios en la propiedad Dirección. Recibirán notificaciones si la propiedad Dirección cambia por sí misma (se asigna un objeto Dirección diferente) pero NO recibirán notificaciones si los datos contenidos por el objeto de dirección anidada (la ciudad o la calle) se modifican.

Esto lleva a la pregunta: ¿se espera que la infraestructura de enlace maneje esto, o debería en mi implementación de Person suscribirme a notificaciones de cambio en el objeto de dirección y luego propagarlas como cambios a "Dirección"?

Si llega a este punto, gracias por solo tomarse el tiempo de leer esta larga pregunta?

Comentario muy apreciado!

Phil

+0

Encontré esta pregunta después de buscar en Google. Para mí, parece que tienes que suscribirte manualmente al evento PropertyChanged para niños y hacerlo funcionar en enlaces WPF. – loraderon

+1

loraderon, estoy bastante seguro de que ese no es el caso, al menos en mis pruebas, ese ha sido el caso. Y, no hay información (que he encontrado) para decir lo contrario. ¿Tiene algún enlace a cualquier información que pueda proporcionar sobre esto? Gracias. Phil – Phil

+0

No tengo ningún enlace tampoco. En mi proyecto actual tuve que hacer burbujas en el evento PropertyChanged para que funcione. Soy un novato en WPF y MVVM, así que podría ser algo especial con mi proyecto. – loraderon

Respuesta

1

Ha respondido a esta pregunta cuando dijiste

... decir utilizando la unión alguien está suscribiendo a cambiar notificación en un objeto Person,

Ese alguien es suscribirse a Person y no tiene manera de saber si la dirección ha cambiado. Así que tendrá que manejar esta situación por su cuenta (lo cual es bastante fácil de implementar).

+1

¿Es ese realmente el caso? Por ejemplo en WPF, que podía hacer esto Aquí (¡si estoy en lo correcto!), la infraestructura vinculante se aseguraría de que los cuadros de texto de la ciudad y la calle se actualicen si la propiedad Dirección cambia o la calle/ciudad cambios. – Phil

+0

Perdón por que xaml no salió muy bien en el comentario. De todos modos, lo que trato de decir es que * podría * ser el requisito de la persona que llama (la entidad que usa el objeto Persona) registrarse para notificaciones de cambio tanto en el objeto persona como en cualquier objeto de propiedad anidado * que el llamante usa *. ¡No estoy diciendo que este sea el caso! ... Por eso formulo la pregunta original porque creo que es posible para dos diseños (ya sea responsabilidad de empuje hacia el usuario o hacia el implementador). He intentado buscar documentación de MS pero no he encontrado nada definitivo. ¡Salud! – Phil

+0

Lo siento, se supone que está usando Winfomrs. No tengo mucho conocimiento de WPF, pero supongo que en WPF también funcionará exactamente de la misma manera. WPF tiene un concepto de eventos que brotan y probablemente tendrá que utilizar ese hecho. Mire este artículo, debería explicarle cómo crear eventos enrutados personalizados http://msdn.microsoft.com/en-us/library/ms742806.aspx –

2

Una de las formas más sencillas de hacerlo es agregar un controlador de eventos a la persona que se encargará de los eventos de notificación de 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")) 
    } 
} 
+2

tkola, sé cómo implementar la notificación de cambio de propiedad para niños. La pregunta es, ¿se requiere esto para que el enlace de datos funcione correctamente? Según lo que he visto, la respuesta parece ser No, no es necesario realizar una notificación de cambio cuando cambian los objetos secundarios: de hecho ya está siendo manejada por la infraestructura de enlace (al menos para WPF) – Phil

+10

Es posible que desee anular la suscripción del el evento PropertyChanged de m_address antes de establecer m_address en un nuevo valor en Address set. –

+0

¿Qué pasa si la dirección es una DependencyProperty? – tofutim

0

Si desea objetos secundarios para ver como si fueran parte de un padre directamente necesita hacer el burbujeo usted mismo.

Para su ejemplo, usted estaría vinculando a 'Address.Street' en su vista, por lo que debe crear una notificación notificada de una propiedad que contenga esa cadena.

Escribí una ayuda fácil para hacer esto. Simplemente llame a BubblePropertyChanged (x => x.BestFriend) en el constructor de su modelo de vista padre. nótese bien. hay una suposición de que tienes un método llamado NotifyPropertyChanged en tu padre, pero puedes adaptarlo para que se adapte.

 /// <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; }); 
    } 
0

una vieja cuestión, sin embargo ...

Mi enfoque original para fijar la propiedad secundaria cambiado a los padres. Esto tiene una ventaja, consumir el evento del padre es fácil. Solo necesita suscribirse 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 la ingeniería complejo adicional. Si el sistema de suscripción tiene la flexibilidad de atravesar estructuras de datos anidados, su aplicabilidad está limitada a niños de 1er nivel.

Aunque 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á finalmente la estructura de datos. soluciones actualmente prefieren como éste: incluso las estructuras de datos complejas

https://github.com/buunguyen/notify

de esa manera son simples y predecibles, que está bajo control de abonado de cómo suscribirse y cómo reaccionar, juega bien con las capacidades de los motores de unión.

Cuestiones relacionadas