2009-08-26 7 views
16

to pytanie pokaże mój brak zrozumienia oczekiwanego zachowania podczas wdrażania/używanie INotifyPropertyChanged:Kiedy zagnieżdżanie właściwości implementujących INotifyPropertyChanged musi zmienić profil obiektu macierzystego?

Pytanie brzmi - do wiązania się działać zgodnie z oczekiwaniami, gdy masz klasy, który sam implementuje INotifyPropertyChanged, które zagnieżdżone właściwości typu INotifyPropertyChanged są oczekiwane, aby wewnętrznie subskrybować powiadomienia o zmianach dla tych właściwości, a następnie włączyć powiadomienia? A może wiążąca infrastruktura, której spodziewamy się, że sprawi, że stanie się to niepotrzebne?

Na przykład (uwaga ten kod nie jest kompletny - tylko za zadanie zilustrować pytanie):

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

Tak więc, w tym przykładzie mamy zagnieżdżonych Adres obiektu w obiekcie osoby. Oba narzędzia implementują INotifyPropertyChanged, dzięki czemu zmiana ich właściwości spowoduje przesyłanie powiadomień o zmianach właściwości do subskrybentów.

Ale powiedzmy, że za pomocą wiązania ktoś subskrybuje powiadomienie o zmianie w obiekcie Person i "nasłuchuje" zmian właściwości adresu. Otrzymają powiadomienia, jeśli sama właściwość Adres zmieni się (przydzielony zostanie inny obiekt Adres), ale NIE BĘDZIE otrzymywać powiadomień, jeśli dane zawarte przez zagnieżdżony obiekt adresu (miasto lub ulica) zostaną zmienione.

Prowadzi to do pytania - czy wiąże się z tym infrastruktura wiążąca, czy też powinienem w ramach mojej realizacji Osoby subskrybować powiadomienia o zmianach w obiekcie adresu, a następnie propagować je jako zmiany w "Adresie"?

Jeśli dojdziesz do tego punktu, dziękuję za poświęcenie czasu na przeczytanie tego zdyszanego pytania?

Komentarz bardzo doceniany!

Phil

+0

Znalazłem to pytanie po przeszukaniu. Wydaje mi się, że musisz ręcznie zasubskrybować zdarzenie PropertyChanged dla dzieci i wypuścić je do pracy w powiązaniach WPF. – loraderon

+1

loraderon, jestem prawie pewien, że tak nie jest - przynajmniej w moich testach, okazało się, że tak jest. I nie ma żadnych informacji (które znalazłem), które wskazywałyby inaczej. Czy masz jakieś linki do jakichkolwiek informacji, które możesz podać w tej sprawie? Dzięki. Phil – Phil

+0

Nie mam też żadnych linków. W moim bieżącym projekcie musiałem zdeformować zdarzenie PropertyChanged, aby działało. Jestem początkującym programistą w WPF i MVVM, więc może to być coś wyjątkowego w moim projekcie. – loraderon

Odpowiedz

1

Odpowiedziałeś na to pytanie, kiedy powiedział

... powiedzmy za pomocą wiązania ktoś subskrypcji zmienić powiadomienie na przedmiotami Osoba,

że ktoś subskrybowanie osoby i nie ma możliwości sprawdzenia, czy adres się zmienił. Musisz więc sam poradzić sobie z tą sytuacją (co jest dość łatwe do wdrożenia).

+1

Czy tak jest naprawdę? Na przykład w WPF, mogę to zrobić Tutaj , (jeśli mam rację!), infrastruktura wiążąca sprawi, że pola tekstowe miasta i ulicy zostaną zaktualizowane, jeśli zmieni się właściwość adresu lub ulica/miasto zmiany. – Phil

+0

Przepraszamy, że Xaml nie wyszedł zbyt dobrze w komentarzu. W każdym razie, staram się powiedzieć, że to * może * być wymaganiem osoby dzwoniącej (podmiotu używającego obiektu Person) do zarejestrowania powiadomień o zmianach zarówno w obiekcie osoby, jak i we wszystkich zagnieżdżonych obiektach właściwości *, z których korzysta wywołujący *. Nie mówię, że tak jest w tym przypadku! ... Dlatego właśnie zadaję oryginalne pytanie, ponieważ uważam, że jest możliwe dla dwóch projektów (albo popychać odpowiedzialność na użytkownika lub na wdrażającego). Próbowałem patrząc na dokumentację MS, ale nie znalazłem nic ostatecznego. Pozdrawiam! – Phil

+0

Przykro mi, założyłem, że używasz Winfomrs. Nie mam dużej wiedzy na temat WPF, ale przypuszczam, że w WPF również będzie działać dokładnie tak samo. WPF ma koncepcję zdarzeń, które się pojawiają i prawdopodobnie będziesz musiał wykorzystać ten fakt. Zobacz ten artykuł, powinien wyjaśnić, aby utworzyć niestandardowe zdarzenia routowane. http://msdn.microsoft.com/en-us/library/ms742806.aspx –

2

Jednym z najprostszych sposobów na to jest dodanie obsługi zdarzeń, aby osoby, które będą obsługiwać zdarzenia zawiadomienie od m_address obiektu:

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, wiem jak wdrożyć powiadomienia o zmianie właściwości dla dzieci. Pytanie brzmi, czy jest to wymagane, aby powiązanie danych działało poprawnie. Z tego, co widziałem, odpowiedź brzmi: Nie - nie musisz wykonywać powiadomienia o zmianie, gdy zmienia się obiekt dziecka: jest on już skutecznie obsługiwany przez powiązaną infrastrukturę (przynajmniej dla WPF). – Phil

+10

Możesz chcieć zrezygnować z subskrypcji stare zdarzenie PropertyChanged m_address przed ustawieniem m_address na nową wartość w zestawie adresów. –

+0

Co zrobić, jeśli adres jest DependencyProperty? – tofutim

0

Jeśli chcesz do obiektów podrzędnych, aby zobaczyć, jak gdyby oni są częścią bezpośrednio ich rodzicowi, musisz samodzielnie wykonać bulgotanie.

Na przykład, wiązałoby się z "Address.Street" w widoku, więc trzeba bańka notifypropertyfikowane zawierające ten ciąg.

Napisałem do tego łatwego pomocnika. Po prostu wywołaj funkcję BubblePropertyChanged (x => x.BestFriend) w konstruktorze modelu widoku rodzica. n.b. istnieje założenie, że masz w rodzicu metodę o nazwie NotifyPropertyChanged, ale możesz ją odpowiednio dostosować.

 /// <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

Stare pytanie, niemniej jednak ...

Moje oryginalne podejście było przyłączenie nieruchomości dziecięcą zmieniona na rodzica. Ma to tę zaletę, że pochłonięcie zdarzenia rodzica jest łatwe. Wystarczy zasubskrybować rodzica.

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

Użycie jest proste:

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

Jednak takie podejście nie jest bardzo elastyczny i nie ma kontroli nad nim, przynajmniej bez dodatkowej skomplikowanej inżynierii. Jeśli system subskrybujący ma swobodę przechodzenia przez zagnieżdżone struktury danych, jego zastosowanie jest ograniczone do dzieci pierwszego poziomu.

Chociaż zastrzeżenia mogą być dopuszczalne, w zależności od zastosowania, od tego czasu odstąpiłem od tego podejścia, ponieważ nigdy nie jest pewne, w jaki sposób ostatecznie zostanie użyta struktura danych. Obecnie preferując rozwiązania, takie jak ten:

https://github.com/buunguyen/notify

ten sposób nawet skomplikowane struktury danych są proste i przewidywalne, to jest pod kontrolą abonenckiej jak subskrybować i jak reagować, to gra dobrze z możliwościami silników wiążących.

Powiązane problemy