it-swarm.com.de

Abonnieren Sie INotifyPropertyChanged für verschachtelte (untergeordnete) Objekte

Ich suche nach einer sauberen und eleganten -Lösung, um das INotifyPropertyChanged-Ereignis von verschachtelten (untergeordneten) Objekten zu behandeln. Beispielcode: 

public class Person : INotifyPropertyChanged {

  private string _firstName;
  private int _age;
  private Person _bestFriend;

  public string FirstName {
    get { return _firstName; }
    set {
      // Short implementation for simplicity reasons
      _firstName = value;
      RaisePropertyChanged("FirstName");
    }
  }

  public int Age {
    get { return _age; }
    set {
      // Short implementation for simplicity reasons
      _age = value;
      RaisePropertyChanged("Age");
    }
  }

  public Person BestFriend {
    get { return _bestFriend; }
    set {
      // - Unsubscribe from _bestFriend's INotifyPropertyChanged Event
      //   if not null

      _bestFriend = value;
      RaisePropertyChanged("BestFriend");

      // - Subscribe to _bestFriend's INotifyPropertyChanged Event if not null
      // - When _bestFriend's INotifyPropertyChanged Event is fired, i'd like
      //   to have the RaisePropertyChanged("BestFriend") method invoked
      // - Also, I guess some kind of *weak* event handler is required
      //   if a Person instance i beeing destroyed
    }
  }

  // **INotifyPropertyChanged implementation**
  // Implementation of RaisePropertyChanged method

}

Konzentrieren Sie sich auf die BestFriend-Eigenschaft und deren Wertesetzer. Jetzt Ich weiß, dass ich dies manuell tun könnte und alle in den Kommentaren beschriebenen Schritte ausführen. Dies wird jedoch eine Menge Code sein, insbesondere wenn ich vorsehe, dass viele untergeordnete Eigenschaften INotifyPropertyChanged wie folgt implementieren. Natürlich werden sie nicht immer vom selben Typ sein, das einzige, was sie gemeinsam haben, ist die INotifyPropertyChanged-Schnittstelle.

Der Grund ist, dass ich in meinem realen Szenario ein komplexes Objekt "Item" (im Einkaufswagen) habe, das Objekteigenschaften über mehrere Ebenen (Element hat ein Objekt "License", das selbst wieder untergeordnete Objekte enthalten kann) und ich enthält Sie müssen sich über jede einzelne Änderung des "Artikels" informieren, um den Preis neu berechnen zu können.

Hast du ein paar gute Tipps oder sogar eine .__ Implementierung, um mir zu helfen, diese zu lösen?

Leider kann/darf ich Post Build-Schritte nicht verwenden, um mein Ziel zu erreichen.

Vielen Dank im Voraus,
- Thomas

27
thmshd

da ich keine gebrauchsfertige Lösung finden konnte, habe ich eine benutzerdefinierte Implementierung auf der Grundlage der Vorschläge von Pieters (und Marks) durchgeführt (Danke!).

Wenn Sie die Klassen verwenden, werden Sie über jede Änderung in einem tiefen Objektbaum benachrichtigt. Dies funktioniert für alle INotifyPropertyChanged-implementierenden Typen und INotifyCollectionChanged *, die Sammlungen implementieren (natürlich verwende ich die ObservableCollection-Komponente).

Ich hoffe, dass dies eine recht saubere und elegante Lösung war, die jedoch noch nicht vollständig getestet wurde, und es gibt Raum für Verbesserungen. Es ist ziemlich einfach zu benutzen, erstellen Sie einfach eine Instanz von ChangeListener, verwenden Sie die statische Create-Methode und übergeben Sie Ihre INotifyPropertyChanged:

var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged += 
    new PropertyChangedEventHandler(listener_PropertyChanged);

PropertyChangedEventArgs liefert eine PropertyName, die immer der vollständige "Pfad" Ihrer Objekte ist. Wenn Sie beispielsweise den "BestFriend" -Namen Ihrer Person ändern, ist die PropertyName "BestFriend.Name". Wenn die BestFriend eine Sammlung von Kindern enthält und Sie ihr Alter ändern, lautet der Wert "BestFriend.Children []. Age". und so weiter. Vergessen Sie nicht, Dispose, wenn Ihr Objekt zerstört wird, dann wird es (hoffentlich) von allen Ereignis-Listenern vollständig abbestellt.

Es wird in .NET (in 4 getestet) und Silverlight (in 4 getestet) kompiliert. Da der Code in drei Klassen aufgeteilt ist, habe ich den Code an Gist 705450 geschrieben, wo Sie alles finden können: https://Gist.github.com/705450 **

*) Ein Grund dafür, dass der Code funktioniert, ist, dass ObservableCollection auch INotifyPropertyChanged implementiert, sonst würde es nicht wie gewünscht funktionieren, dies ist eine bekannte Einschränkung

**) Kostenlose Nutzung, veröffentlicht unter MIT Lizenz

21
thmshd

Ich denke, was Sie suchen, ist so etwas wie WPF-Bindung.

Wie INotifyPropertyChanged funktioniert, ist, dass die RaisePropertyChanged("BestFriend");only forciert werden muss, wenn sich die Eigenschaft BestFriend ändert. Nicht, wenn sich etwas am Objekt selbst ändert.

Wie Sie dies implementieren würden, ist eine INotifyPropertyChanged-Ereignisprozedur in zwei Schritten. Ihr Listener würde sich für das geänderte Ereignis der Person registrieren. Wenn die BestFriend eingestellt/geändert wird, registrieren Sie sich für das geänderte Ereignis der BestFriendPerson. Dann hören Sie auf geänderte Ereignisse dieses Objekts.

Genau so setzt die WPF-Bindung dies um. Das Abhören von Änderungen verschachtelter Objekte erfolgt über dieses System.

Der Grund, warum dies nicht funktioniert, wenn Sie es in Person implementieren, besteht darin, dass die Ebenen sehr tief werden können und das geänderte Ereignis von BestFriend nichts mehr bedeutet ("Was hat sich geändert?"). Dieses Problem wird größer, wenn Sie zirkuläre Beziehungen haben, bei denen z. Der beste Freund deines Bruders ist die Mutter deines besten Freundes. Wenn sich dann eine der Eigenschaften ändert, kommt es zu einem Stapelüberlauf.

So lösen Sie dieses Problem: Erstellen Sie eine Klasse, mit der Sie Listener erstellen können. Sie würden beispielsweise einen Listener auf BestFriend.FirstName erstellen. Diese Klasse würde dann einen Ereignishandler auf das geänderte Ereignis von Person setzen und sich die Änderungen auf BestFriend anhören. Wenn sich das ändert, setzt es einen Listener auf BestFriend und wartet auf Änderungen von FirstName. Wenn sich das ändert, wird ein Ereignis ausgelöst, und Sie können sich das anhören. So funktioniert die WPF-Bindung.

Weitere Informationen zur WPF-Bindung finden Sie unter http://msdn.Microsoft.com/de-de/library/ms750413.aspx .

16

Interessante Lösung Thomas. 

Ich habe eine andere Lösung gefunden. Es heißt Propagator Design Pattern. Weitere Informationen finden Sie im Internet (z. B. auf CodeProject: Propagator in C # - Eine Alternative zum Observer Design Pattern ).

Im Grunde handelt es sich dabei um ein Muster zum Aktualisieren von Objekten in einem Beziehungsnetzwerk. Dies ist sehr nützlich, wenn Statusänderungen durch ein Objektnetzwerk übertragen werden müssen. Eine Zustandsänderung wird durch ein Objekt selbst dargestellt, das sich durch das Netzwerk der Propagatoren bewegt. Durch das Einkapseln der Zustandsänderung als Objekt werden die Propagatoren lose gekoppelt.

Ein Klassendiagramm der wiederverwendbaren Propagator-Klassen:

A class diagram of the re-usable Propagator classes

Lesen Sie mehr über CodeProject .

3
Marek Takac

Überprüfen Sie meine Lösung zu CodeProject: http://www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator Sie macht genau das, was Sie brauchen - hilft bei der Verbreitung (auf elegante Weise) abhängige Eigenschaften, wenn sich relevante Abhängigkeiten in diesem oder in verschachtelten Ansichtsmodellen ändern:

public decimal ExchTotalPrice
{
    get
    {
        RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice));
        RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate));
        return TotalPrice * ExchangeRate.Rate;
    }
}
0
KolA

Ich habe dazu einen einfachen Helfer geschrieben. Sie rufen einfach BubblePropertyChanged (x => x.BestFriend) in Ihrem übergeordneten Ansichtsmodell auf. n.b. Es gibt eine Annahme, dass Sie über eine Methode namens NotifyPropertyChagned in Ihrem übergeordneten Element verfügen, die Sie jedoch anpassen können.

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

Ich habe jetzt schon einen Tag im Web gesucht und eine andere Nice-Lösung von Sacha Barber gefunden:

http://www.codeproject.com/Articles/166530/A-Chained-Property-Observer

Er hat schwache Referenzen innerhalb eines Chained Property Observer erstellt. Überprüfen Sie den Artikel, wenn Sie eine weitere gute Möglichkeit zur Implementierung dieser Funktion sehen möchten.

Ich möchte auch eine schöne Implementierung mit den Reactive Extensions @ http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/ erwähnen.

Diese Lösung funktioniert nur für eine Ebene des Beobachters, nicht für eine ganze Kette von Beobachtern. 

0
Pascalsz