2010-08-26 10 views
7

Ich versuche zu verstehen, wie die Benutzeroberfläche zu aktualisieren ist, wenn ich eine schreibgeschützte Eigenschaft habe, die von einer anderen Eigenschaft abhängig ist, so dass Änderungen an einer Eigenschaft beide UI-Elemente aktualisieren (in diesem Fall) eine Textbox und eine nur-Lese-Textbox. zum Beispiel:WPF INotifyPropertyChanged für verknüpfte schreibgeschützte Eigenschaften

public class raz : INotifyPropertyChanged 
{ 

    int _foo; 
    public int foo 
    { 
    get 
    { 
     return _foo; 
    } 
    set 
    { 
     _foo = value; 
     onPropertyChanged(this, "foo"); 
    } 
    } 

    public int bar 
    { 
    get 
    { 
     return foo*foo; 
    } 
    } 

    public raz() 
    { 

    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    private void onPropertyChanged(object sender, string propertyName) 
    { 
    if(this.PropertyChanged != null) 
    { 
     PropertyChanged(sender, new PropertyChangedEventArgs(propertyName)); 
    } 
    } 
} 

Mein Verständnis ist, dass Bar wird nicht automatisch die Benutzeroberfläche aktualisiert, wenn foo modifiziert Was ist der richtige Weg, auf diese Weise

Antwort

6

One zu tun.? zeigen an, dass die Bar geändert wurde, ist ein Anruf zu onPropertyChanged(this, "bar") im foo Setter hinzuzufügen. Hässlich wie die Hölle, ich weiß, aber da hast du es.

Wenn foo in einer Vorgängerklasse definiert ist oder Sie anderweitig keinen Zugriff auf die Implementierung des Setter haben, können Sie das PropertyChanged-Ereignis abonnieren, so dass Sie auch eine Änderung "foo" sehen können feuern Sie eine "bar" Änderungsbenachrichtigung ab. Das Abonnieren von Ereignissen auf Ihrer eigenen Objektinstanz ist gleichermaßen hässlich, aber es wird die Aufgabe erledigen.

+2

I verstehst du nicht, warum du es für besonders hässlich hältst, onPropertyChanged (das, "bar") zu feuern? Ich konnte es sehen, wenn .bar von 3 Eigenschaften abhängig war und Sie diesen Aufruf in allen drei Setter hatten ... aber dann konnte er die Berechnung aus dem Eigenschaft-Getter von .bar und in eine Berechnungsmethode verschieben, die das Update aufruft Methode. – Goblin

+4

Es ist hässlich, weil Sie Logik platzieren, die für das richtige Verhalten der Eigenschaft B im Setter von Eigenschaft A erforderlich ist. Es kann eine beliebige Anzahl von Eigenschaften geben, die vom Wert von A abhängen. Sie müssen A ändern, um die Anforderungen anderer Eigenschaften zu erfüllen Diese Verwendung von A ist nicht naheliegend und nicht skalierbar. – dthorpe

+1

Eine sauberere Lösung wäre eine Art zu zeigen, dass B von A abhängig ist und dass, wenn A sich ändert, B auch eine Wertänderung signalisieren sollte. Die zweite Option oben, um das eigene PropertyChanged-Ereignis anzuhören, ist in diesem Stil, aber das Zuhören Ihrer eigenen Events ist auch irgendwie kinky. – dthorpe

0

Abhängig von den Kosten der Berechnung und wie häufig Sie erwarten, dass es verwendet wird, kann es vorteilhaft sein, es zu einer privaten Set-Eigenschaft zu machen und den Wert zu berechnen, wenn foo festgelegt wird, anstatt die bar zu berechnen bekommen heißt. Dies ist im Grunde eine Caching-Lösung, und Sie können dann die Benachrichtigung über die Änderung von Eigenschaften als Teil des privaten Setter bar tun. Ich bevorzuge diesen Ansatz im Allgemeinen, hauptsächlich weil ich AOP (über Postsharp) verwende, um die tatsächliche INotifyPropertyChanged Boilerplate zu implementieren.

-Dan

5

könnten Sie einfach

OnPropertyChanged(this, "bar"); 

von überall in dieser Klasse nennen ... Sie kalt, auch so gehen:

public raz() 
    { 
     this.PropertyChanged += new PropertyChangedEventHandler(raz_PropertyChanged); 
    } 

    void raz_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if(e.PropertyName == "foo") 
     { 
      onPropertyChanged(this, "bar"); 
     } 
    } 
+0

Dies scheint eine bessere skalierbare Lösung zu sein, wenn ich für eine Reihe von Eigenschaften wiederholen muss. – tbischel

+1

das Abonnieren des PropertyChanged-Ereignisses und das Auslösen der Barwechselbenachrichtigung ist genau das, was PRISM in ihrem Commanding Quickstart macht.Ihr Beispiel ist besser - if (propertyName == "Price" || propertyName == "Quantity" || propertyName == "Versand") { this.NotifyPropertyChanged ("Total"); } – RichardOD

8

Wenn dies ein ernstes Problem (mit "ernst", ich meine, Sie haben eine nicht-triviale Anzahl von abhängigen schreibgeschützten Eigenschaften), können Sie eine Abhängigkeitskarte erstellen, zB:

private static Dictionary<string, string[]> _DependencyMap = 
    new Dictionary<string, string[]> 
{ 
    {"Foo", new[] { "Bar", "Baz" } }, 
}; 

und dann verweisen sie in OnPropertyChanged:

PropertyChanged(this, new PropertyChangedEventArgs(propertyName)) 
if (_DependencyMap.ContainsKey(propertyName)) 
{ 
    foreach (string p in _DependencyMap[propertyName]) 
    { 
     PropertyChanged(this, new PropertyChangedEventArgs(p)) 
    } 
} 

Dies ist nicht von Natur aus viel anders als nur mehrere Putting OnPropertyChanged Anrufe in der Foo Setter, da man die Abhängigkeit der Karte für jeden neuen abhängig aktualisieren müssen Eigenschaft, die Sie hinzufügen.

Aber es macht es möglich, eine PropertyChangeDependsOnAttribute nachträglich zu implementieren und Reflektion verwenden, um den Typ zu scannen und die Abhängigkeitskarte zu erstellen. So würde Ihre Eigenschaft etwa wie folgt aussehen:

+0

Können Sie Ihre Implementierung des 'PropertyChangeDependsOn' Systems anzeigen? – Shimmy

+0

Leider habe ich nur behauptet, dass es möglich ist, es zu implementieren. Es zu implementieren, ist nicht etwas, was ich tun musste. –

+0

Danke. Ich habe es selbst implementiert, ich hatte wenig Mühe, einen effizienten Cache zu erstellen, um zu vermeiden, die Beziehungen mit Reflektion neu zu laden. – Shimmy

2

Wenn Sie nur Bar für UI-Zwecke verwenden, können Sie es vollständig aus Ihrem Modell entfernen. Sie könnten das UI-Element an die Eigenschaft foo binden und einen benutzerdefinierten Wertkonverter verwenden, um das Ergebnis von foo in foo * foo zu ändern.

In WPF gibt es oft eine Menge Möglichkeiten, um das gleiche zu erreichen. Oft gibt es keinen richtigen Weg, nur eine persönliche Präferenz.

+0

in meinem Fall muss der Benutzer es sehen und das Programm muss auch darauf zugreifen, aber das klingt interessant. – tbischel

0

Ich bin ziemlich sicher, dass es in einer deklarativen Art möglich sein muss, aber mein erster Versuch, dieses Problem zu lösen, war ein Misserfolg. Die Lösung, die ich erreichen möchte, verwendet Lambda Expressions, um dies zu definieren. Da Ausdrücke analysiert werden können (??), sollte es möglich sein, die Ausdrücke zu analysieren und an alle NotifyPropertyChanged-Ereignisse anzuhängen, um über Änderungen von abhängigen Daten benachrichtigt zu werden.

in ContinousLinq, das funktioniert hervorragend für Sammlungen.

private SelfUpdatingExpression<int> m_fooExp = new SelfUpdatingExpression<int>(this,()=> Foo * Foo); 

public int Foo 
{ 
    get 
    { 
     return m_fooExp.Value; 
    } 
} 

aber unfortionatly i fehle auf fundamentional wissen, wie in Ausdrücken und Linq :(

+0

Kann WPF direkt gegen den sich selbst aktualisierenden Ausdruck binden? - Wenn ja, warum nicht einfach diesen Typ anstelle des berechneten int zurückgeben? – BrainSlugs83

9

Ich weiß, dies ist eine alte Frage, aber es ist das erste Google-Ergebnis von „NotifyPropertyChanged verknüpfter Eigenschaften“, so dass ich denke, es ist angemessen, diese Antwort hinzufügen, so dass es einige konkrete Code ist

I Robert Rossney Vorschlag verwendet und ein benutzerdefiniertes Attribut erstellt, verwendet es dann in einem Property Veranstaltung Basisansicht Modell

Die Attributklasse..:

Und in meinem Basisansicht Modell (das alle anderen Modelle WPF Ansicht erben von):

public abstract class BaseViewModel : INotifyPropertyChanged 
{ 
    protected Dictionary<string, List<string>> DependencyMap; 

    protected BaseViewModel() 
    { 
     DependencyMap = new Dictionary<string, List<string>>(); 

     foreach (var property in GetType().GetProperties()) 
     { 
      var attributes = property.GetCustomAttributes<DependsOnPropertyAttribute>(); 
      foreach (var dependsAttr in attributes) 
      { 
       if (dependsAttr == null) 
        continue; 

       var dependence = dependsAttr.Dependence; 
       if (!DependencyMap.ContainsKey(dependence)) 
        DependencyMap.Add(dependence, new List<string>()); 
       DependencyMap[dependence].Add(property.Name); 
      } 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     var handler = PropertyChanged; 
     if (handler == null) 
      return; 

     handler(this, new PropertyChangedEventArgs(propertyName)); 

     if (!DependencyMap.ContainsKey(propertyName)) 
      return; 

     foreach (var dependentProperty in DependencyMap[propertyName]) 
     { 
      handler(this, new PropertyChangedEventArgs(dependentProperty)); 
     } 
    } 
} 

Dies ermöglicht es mir jetzt Eigenschaften leicht wie so, markieren:

public int NormalProperty 
{ 
    get {return _model.modelProperty; } 
    set 
    { 
     _model.modelProperty = value; 
     OnPropertyChanged(); 
    } 
} 

[DependsOnProperty(nameof(NormalProperty))] 
public int CalculatedProperty 
{ 
    get { return _model.modelProperty + 1; } 
} 
+0

Was ist, wenn ich die Eigenschaft AnyState in Klasse A habe. Das ist im Grunde genommen ein Get von zwei anderen Eigenschaften, jedes in einer separaten Klasse. prop A get {return B.IsRunning || C.IsRunning;} Klasse B Klassen eine IsRunning-Eigenschaft und C sowie. – Luishg

+0

@Luishg Wenn sich die Eigenschaften in verschiedenen Klassen befinden, benötigen Sie eine andere Lösung. Dieses Attribut ist nur für berechnete Eigenschaften einer einzelnen Klasse gedacht. –