2009-08-26 5 views
16

questa domanda sta per mostrare la mia mancanza di comprensione del comportamento previsto in sede di attuazione/utilizza INotifyPropertyChanged:Quando si nidificano le proprietà che implementano INotifyPropertyChanged, l'oggetto principale propogate cambia?

La domanda è - per il legame a funzionare come previsto, quando si dispone di una classe che si implementa INotifyPropertyChanged, che ha nidificato proprietà di tipo INotifyPropertyChanged ci si aspetta di iscriversi internamente per modificare la notifica di queste proprietà e quindi propogare le notifiche? Oppure l'infrastruttura vincolante dovrebbe avere la capacità di rendere questo non necessario?

Per esempio (notare che questo codice non è completa - solo scopo di illustrare la questione):

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

Quindi, in questo esempio abbiamo un oggetto Indirizzo annidato in un oggetto Person. Entrambi implementano INotifyPropertyChanged in modo che l'alterazione delle loro proprietà comporti la trasmissione di notifiche di modifica delle proprietà ai sottoscrittori.

Ma diciamo che utilizzando l'associazione qualcuno sta sottoscrivendo per modificare la notifica su un oggetto Persona e sta "ascoltando" per le modifiche alla proprietà Indirizzo. Riceveranno notifiche se la proprietà Address stessa cambia (viene assegnato un oggetto Address diverso) ma NON riceverà notifiche se i dati contenuti nell'oggetto di indirizzo nidificato (la città o la strada) vengono modificati.

Questo porta alla domanda: l'infrastruttura di binding è prevista per la gestione di questo problema oppure dovrei sottoscrivere alla mia implementazione di Person per modificare le notifiche sull'oggetto indirizzo e quindi propagarle come modifiche a "Indirizzo"?

Se arrivi a questo punto, grazie per aver dedicato del tempo a leggere questa lunga domanda?

Commento molto apprezzato!

Phil

+0

Ho trovato questa domanda dopo aver cercato su Google. A me sembra che tu debba iscriversi manualmente all'evento PropertyChanged dei bambini e farlo circolare per funzionare con i binding WPF. – loraderon

+1

loraderon, sono abbastanza sicuro che non sia così - almeno nei miei test, che è risultato essere il caso. E, non ci sono informazioni (che ho trovato) per dire altrimenti. Hai qualche link per qualsiasi informazione che puoi fornire su questo? Grazie. Phil – Phil

+0

Non ho alcun collegamento neanche. Nel mio progetto attuale ho dovuto imbastire l'evento PropertyChanged per farlo funzionare. Sono un neofita di WPF e MVVM quindi potrebbe essere solo qualcosa di speciale con il mio progetto. – loraderon

risposta

1

È risposto a questa domanda quando hai detto

... dire usando vincolante qualcuno sta sottoscrivendo cambiare notifica un oggetto Person,

che qualcuno sta abbonarsi alla persona e non ha modo di sapere se l'indirizzo è cambiato. Quindi dovrai gestire da solo questa situazione (che è abbastanza facile da implementare).

+1

È davvero così? Per esempio in WPF, avrei potuto fare questo Qui , (se sono corretto!), l'infrastruttura vincolante dovrebbe assicurarsi che le caselle di testo di città e strade vengano aggiornate se la proprietà Address cambia o Street/City i cambiamenti. – Phil

+0

Mi dispiace che xaml non sia uscito troppo bene nel commento. Ad ogni modo, quello che sto cercando di dire è che * potrebbe * essere il requisito del chiamante (l'entità che usa l'oggetto Person) di registrarsi per le notifiche di modifica sia sull'oggetto persona che su qualsiasi oggetto di proprietà nidificata * utilizzato dal chiamante *. Non sto dicendo che questo è il caso! ... Ecco perché chiedo la domanda originale perché credo che sia possibile per due progetti (o spingere la responsabilità sull'utente o sull'implementatore). Ho provato a consultare la documentazione MS ma non ho trovato nulla di definitivo. Saluti! – Phil

+0

Mi dispiace, presumo che stai usando Winfomrs. Non ho molta conoscenza di WPF, ma la mia ipotesi è che anche in WPF funzionerà esattamente allo stesso modo. WPF ha il concetto di eventi che si gonfiano e probabilmente dovrai usare questo fatto. Guarda questo articolo, dovrebbe spiegarti come creare eventi instradati personalizzati http://msdn.microsoft.com/en-us/library/ms742806.aspx –

2

Uno dei modi più semplici per farlo è quello di aggiungere un gestore di eventi per persona che gestirà gli eventi di notifica da oggetto 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, so come implementare la notifica di modifica della proprietà per i bambini. La domanda è, è necessario per il corretto funzionamento del binding dei dati. Da quello che ho visto, la risposta sembra essere No - non è necessario eseguire la notifica di modifica quando si modifica l'oggetto figlio: è effettivamente già gestito dall'infrastruttura di bind (almeno per WPF) – Phil

+10

Si potrebbe voler annullare l'iscrizione dal vecchio evento PropertyChanged di m_address prima di impostare m_address su un nuovo valore in Address set. –

+0

Cosa succede se Address è una DependencyProperty? – tofutim

0

Se si desidera oggetti figlio per vedere come se fossero parte di un genitore direttamente hai bisogno di fare il bubbling te stesso.

Per il tuo esempio, si sarebbe vincolante a "Address.Street" nella propria vista, quindi è necessario creare una bolla di notifica inviata conterrà quella stringa.

Ho scritto un semplice aiuto per farlo. Chiama semplicemente BubblePropertyChanged (x => x Bestest amico) nel costruttore del modello di vista principale. N.B. c'è un presupposto che tu abbia un metodo chiamato NotifyPropertyChanged nel tuo genitore, ma puoi adattarlo per adattarlo.

 /// <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 vecchia questione, comunque ...

Il mio approccio originale era di allegare proprietà figlio cambiato al genitore. Questo ha un vantaggio, consumare l'evento del genitore è facile. Basta iscriversi al genitore.

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

L'utilizzo è semplice:

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

Tuttavia, questo approccio non è molto flessibile e non v'è alcun controllo su di esso, almeno senza l'ingegneria complessa aggiuntivo. Se il sistema di sottoscrizione ha la flessibilità di attraversare strutture di dati annidati, la sua applicabilità è limitata ai bambini di primo livello.

Mentre le avvertenze possono essere accettabili, a seconda dell'utilizzo, da allora mi sono allontanato da questo approccio, poiché non è mai sicuro di come verrà utilizzata la struttura dati. soluzioni attualmente preferiscono come questo:

https://github.com/buunguyen/notify

In questo modo anche le strutture dati complesse sono semplici e prevedibili, è sotto controllo abbonato come iscriversi e come reagire, gioca bene con le capacità dei motori di legame.

Problemi correlati