Binding wissen lassen, dass Property geändert hat


DrMueller

Erfahrenes Mitglied
#1
Hallo Leute,

ich habe mich gerade mit dem hervorragenden Webcast von Bernd Marquardt (http://www.microsoft.com/germany/msdn/webcasts/serien/MSDNWCS-0809-01.mspx) in das Databinding eingearbeitet.
Zur Übung habe ich eine kleine Applikation geschrieben, welche folgendes macht:
Es können x Veranstaltungen definiert werden, konkret Poker-Turniere, welche regelmässig statt finden, dazu können pro Veranstaltung eben X Sitzungen definiert werden. Nachfolgend ist eine Veranstaltung ein Event und eine Sitzung eine Session.

In der Listbox zur Auswahl der Veranstaltung sieht mein Template so aus:

Code:
    <Window.Resources>
        <code:EventsTemplateSelector x:Key="eventTempSel" />
        
        <DataTemplate x:Key="dtPositive">
            <StackPanel>
                <TextBlock Text="{Binding Path=Name}" FontSize="20" />
                <TextBlock Text="{Binding Path=Description}" FontSize="16" />
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Gewinn: " />
                    <TextBlock x:Name="txtProfit" Text="{Binding Path=Profit}" Foreground="Green" />      
                </StackPanel>           
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Sitzungen: " />
                    <TextBlock x:Name="txtSessions" Text="{Binding Path=SessionsCount}" />
                </StackPanel>
            </StackPanel>
            
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=SessionsCount}" Value="0">
                    <Setter TargetName="txtSessions" Property="Text" Value="Keine Sitzungen" />
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>

        <DataTemplate x:Key="dtNegative">
            <StackPanel>
                <TextBlock Text="{Binding Path=Name}" FontSize="20" />
                <TextBlock Text="{Binding Path=Description}" FontSize="16" />
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Gewinn: " />
                    <TextBlock x:Name="txtProfit" Text="{Binding Path=Profit}" Foreground="Red" />
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Sitzungen: " />
                    <TextBlock x:Name="txtSessions" Text="{Binding Path=SessionsCount}" />
                </StackPanel>
            </StackPanel>

            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=SessionsCount}" Value="0">
                    <Setter TargetName="txtSessions" Property="Text" Value="Keine Sitzungen" />
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>
Daher, wenn der Gewinn negativ ist, wird die Zahl rot dargestellt, ansonsten grün.
Ich nehme an, dies wäre evtl. auch durch Trigger möglich, allerdings habe ich nicht rausgefunden, ob man irgendwie auf >= oder so etwas ähnliches prüfen kann bei Value.

Das Problem ist nun, dass Profit ein ReadOnly Property ist, welches sich im Event durch alle Sitzungen berechnet:

Code:
        public int Profit
        {
            get
            {
                return calculateProfit();
            }
        }

        private int calculateProfit()
        {
            int profit = 0;
            foreach (Session s in Sessions)
                profit += s.Profit;

            return profit;
        }
Dadurch bekommt aber das Binding nicht mit, wenn sich der Proft eines Events ändert. Ich kann zwar das INotifyChanged bei der Sitzung implementieren, aber dem Event ist dies ja egal.

Gibt es da eine einfache Möglichkeit dies irgendwie weiterzuleiten, dass sich da etwas geändert hat, obwohl das Property ReadOnly ist?


Wie immer vielen Dank im Voraus



Müller Matthias
 

Shakie

Erfahrenes Mitglied
#2
Die Veranstaltung muss von jeder Sitzung darüber informiert werden, wenn sich "Profit" einer Sitzung ändert. Anschließend muss die Veranstaltung das INotifyChanged.PropertyChanged-Event auslösen.
Alternativ kannst du nach "Multibinding" suchen. Wenn du einen IMultiValueConverter implementierst, dann kannst du direkt an die einzelnen Sitzungen binden. Hier ein Beispiel.
(Übrigens wäre es vielleicht geschickter einen anderen Namen als "Event" für die Klasse zu wählen, die eine Veranstaltung repräsentiert. Sonst verwirrst du jeden, der deinen Code lesen soll. Das Wort "Event" solltest du nur zur Kennzeichnung programmatischer Ereignisse verwenden.)
 
Zuletzt bearbeitet:

DrMueller

Erfahrenes Mitglied
#3
Ja du hast Recht, Event war unglücklich. Glücklicherweise brauche ich das Programm ja nur als Test, ist daher nicht so tragisch.
Wegen dem Notfity:
Das Event hält ja nur eine Liste von Sessions:

Code:
public ObservableCollection<Session> Sessions { get; set; }
Muss ich daher hier eine eigene Liste erstellen, und wenn eine Session ändert, muss das NotifyChanged der Liste schiessen, welches wiederum im Evet abgefangen werden kann?
 

Shakie

Erfahrenes Mitglied
#4
Muss ich daher hier eine eigene Liste erstellen, und wenn eine Session ändert, muss das NotifyChanged der Liste schiessen, welches wiederum im Evet abgefangen werden kann?
Das hängt ein bisschen davon ab, wie deine Klassen strukturiert sind.
Prinzipiell sollte diejenige Klasse das PropertyChanged-Event auslösen, welche auch die Profit-Eigenschaft besitzt.
INotifyPropertyChanged muss dabei aber nur von der Klasse implementiert werden, die du an WPF weiterreichtst. Sessions müssen nur herkömmliche Events haben, kein PropertyChanged-Event, wenn du mittels WPF nicht auf Sessions, sondern nur auf Events (Veranstaltungen) zugreifst.
Gegebenenfalls bietet sich an, eine Klasse zu erstellen, die von ObservableCollection<Session> erbt und eine Profit-Eigenschaft bereit stellt. ObservableCollection<T> implementiert bereits INotifyPropertyChanged, du musst dann also nur im Event-Handler für alle Sessions das PropertyChanged-Event für "Profit" auslösen (mittels PropertyChangedEventArgs).
 
Zuletzt bearbeitet:

DrMueller

Erfahrenes Mitglied
#5
Hm, sorey, dass ich nochmal nachfrage, was ich nicht ganz sehe:

Meiner Session class, sage ich, dass es PropertyChanged implementieren soll, und zwar so:

Code:
        public int Profit
        {
            get{
                return profit;
            }
            set{
                this.profit = value;
                onPropertyChanged("Profit");
            }
        }

        private void onPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
Wenn ich dann von der Collection ableite:

Code:
    public class SessionObservableCollection:ObservableCollection<Session>
    {
        protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
        }

        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);
        }
    }

Schiesst dann OnPropertyChanged trotzdem nicht. Laut Beschreibung sollte OnPropertyChanged ja schiessen, wenn eine Property Value in der Liste geändert hat, was ja auch der Fall ist.

Oder mache ich hier einen Denkfehler?
 

Shakie

Erfahrenes Mitglied
#6
Du verwechselst hier die Liste mit den Listeninhalten.

Wenn sich eine Eigenschaft eines Objekts ändert (z.B. die Profit-Eigenschaft einer Session-Instanz), welches sich in einer Liste (z.B. ObservableCollection<Session>) befindet, dann ändert sich keine Eigenschaft der Liste. Eigenschaften der Liste sind nur diese hier: Count, Item und Items. Da sich die Werte von diesen Eigenschaften nicht geändert haben, muss die Liste auch nicht OnPropertyChanged aufrufen.

Um Verwirrung zu vermeiden, nenne ich die Profit-Eigenschaft des Events in "TotalProfit" um. Das heißt, TotalProfit=calculateProfit().

In deiner SessionObservableCollection-Klasse musst du also für jedes Element in der Liste (sprich: für jede Session) einen Event-Handler erzeugen, der das Session.ProfitChanged-Event abonniert (welches du zuvor in der Session-Klasse implementieren musst).
In diesem Event-Handler wird dann OnPropertyChanged("TotalProfit") aufgerufen.

Es ist dann nicht nötig, INotifyPropertyChanged in der Session-Klasse zu implementieren.
 

DrMueller

Erfahrenes Mitglied
#7
ok danke, muss ich mir nochmal durch den Kopf gehen lassen.

Evtl. noch eine Frage: Die Observable hat ja zwei Events:
CollectionChanged und PropertyChanged.

Meine Überlegung war nun, dass PropertyChanged schiesst, wenn ein Item in der Collection ändert. Dies ist ja nicht der Fall, worin unterscheiden sich dann die beiden Events noch?