## 21.2 Einfache Datenbindung Im einfachsten Fall einer Datenbindung können wir die Eigenschaften von zwei Komponenten miteinander verbinden. Eine definierte Eigenschaft einer Komponente dient als Datenquelle, und die Eigenschaft einer anderen Komponente ist der Datenempfänger, gewissermaßen also der Konsument. Nehmen wir das folgende Beispiel. In einem Window sind zwei Textboxen integriert. Der Text, der in der oberen TextBox eingetragen wird, soll auch in der unteren TextBox angezeigt werden – und das bei jedem Tastendruck.
    // ---------------------------------------------------------
    // Beispiel: ...\Kapitel 21\SimpleBinding
    // ---------------------------------------------------------
    <Window ...>
      <StackPanel>
        <TextBox Name="txtOben" Height="50"
                 FontSize="16"></TextBox>
        <TextBox Name="txtUnten" Height="50"
                 Background="AliceBlue" FontSize="16">
          <Binding ElementName="txtOben" Path="Text" />
        </TextBox>
      </StackPanel>
    </Window>
Hier klicken, um das Bild zu vergrößern Abbildung 21.1 Ausgabe des Beispiels »SimpleBinding« Für die Bindung sorgt der XAML-Code:
    Binding ElementName="txtOben" Path="Text
Mit einem Binding-Objekt wird die Bindung beschrieben. Dessen Eigenschaft ElementName gibt den Namen der Datenquelle an, und Path beschreibt die Eigenschaft der Datenquelle, an die gebunden werden soll. Mit dieser Bindung wird also die Eigenschaft Text der unteren Textbox an die Eigenschaft Text einer Komponente mit dem Bezeichner txtOben gebunden. Es gibt noch eine andere Schreibweise, bei der die Bindung in geschweiften Klammern beschrieben wird. Diese Notation haben wir schon in einigen Beispielen zuvor benutzt:
    <TextBox Name="txtUnten" Height="50"
             Background="AliceBlue" FontSize="16"
             Text="{Binding ElementName=txtOben, Path=Text}">
Diese Schreibweise wird auch als Binding Markup Extension bezeichnet. Beachten Sie bei dieser Notation, dass die Name-Wert-Paare durch ein Komma getrennt werden. Eine Voraussetzung für die Datenbindung ist, dass die Zieleigenschaft eine abhängige Eigenschaft ist. Die Zieleigenschaft bestimmt auch den Datentyp der Datenbindung. Ist eine implizite Konvertierung des Datentyps aus der Quelle in den Datentyp des Ziels nicht möglich, wird der Code als fehlerhaft bemängelt. ### 21.2.1 Bindung mit Code erzeugen Bindungen, die im XAML-Code definiert sind, wirken sich sofort aus. Möchten Sie eine Bindung erst zur Laufzeit aktivieren, bietet sich die Erstellung im Code an. Zuerst wird dazu ein Binding-Objekt erzeugt. Dem Konstruktor kann man sofort die Zieleigenschaft mitteilen. Der Eigenschaft ElementName wird der Name der Quellkomponente als Zeichenfolge übergeben, und anschließend wird die Methode SetBindung der Zielkomponente aufgerufen. SetBinding erwartet im ersten Parameter den Typ der Datenquelle und deren Eigenschaft, die als Datenquelle betrachtet werden soll, und dem zweiten Parameter übergeben Sie das Binding-Objekt.
    // ---------------------------------------------------------
    // Beispiel: ...\Kapitel 21\SimpleBindingWithCode
    // ---------------------------------------------------------
    <Window ...>
      <StackPanel>
        <TextBox Name="txtOben" Height="50" FontSize="16">
        </TextBox>
        <TextBox Name="txtUnten" Height="50"
                 Background="AliceBlue" FontSize="16" >
        </TextBox>
        <Button Height="23" Name="button1" Width="150"
                HorizontalAlignment="Right"
                Click="button1_Click">Bindung erzeugen
        </Button>
      </StackPanel>
    </Window>
    // C#-Programmcode
    private void button1_Click(object sender, RoutedEventArgs e) {
      Binding binding = new Binding("Text");
      binding.ElementName = "txtOben";
      txtUnten.SetBinding(TextBox.TextProperty, binding);
    }
### 21.2.2 Bindungsrichtung In den letzten Beispielen haben Sie gesehen, wie Sie sehr einfach Eigenschaften verschiedener Objekte miteinander verbinden können. Bindungen gehen aber in ihren Fähigkeiten noch weiter. Mit dem Attribut Mode können Sie festlegen, in welchen Richtungen die Bindung aktiv ist, zum Beispiel:
    <TextBox Name="TextBox1"
         Text="{Binding ElementName=TextBox2, Path=Text, Mode=OneWay}" />
Durch Angabe des Bindungsmodus lassen sich nicht nur Änderungen der Datenquelle in der Benutzeroberfläche darstellen. Auch Änderungen, die der Anwender vornimmt, können sofort zur Aktualisierung der Datenquelle führen. Das Attribut Mode kann auf einen Wert der Enumeration BindingMode eingestellt werden. Die möglichen Werte können Sie Tabelle 21.2 entnehmen.

Tabelle 21.2 Bindungsarten der Enumeration »BindingMode«

Bindungstyp | Beschreibung
Default | Hiermit wird der Standardmodus beschrieben, der für die Eigenschaft vordefiniert ist. Dabei handelt es sich meistens um den Mode TwoWay.
OneTime | Der Wert wird nur einmal von der Quelle zum Ziel übertragen. Danach findet keine Aktualisierung mehr statt. Geeignet ist dieser Modus bei statischen Daten oder bei einem Snapshot der Daten.
OneWay | Der Wert wird nur von der Quelle zum Ziel übertragen. Ändern sich die Daten im Zielobjekt, wird die datengebundene Zieleigenschaft Kenntnis von der Änderung nehmen und angepasst.
OneWayToSource | Der Wert wird von der Quelle zum Ziel übertragen. Ändert sich der Wert im Datenziel, wird auch die Datenquelle aktualisiert.
TwoWay | Die Werte sowohl von der Quelle als auch vom Ziel werden in beide Richtungen übertragen.
Am besten lassen sich die Einstellungen verstehen, wenn der Effekt in einem Beispiel demonstriert wird. Dazu dient das folgende Beispielprogramm Bindungsarten, dessen Laufzeitdarstellung Sie in Abbildung 21.2 sehen.
    // ---------------------------------------------------------
    // Beispiel: ...\Kapitel 21\Bindungsmodus
    // ---------------------------------------------------------
    <Window ...>
      <Window.Resources>
        <Style TargetType="{x:Type TextBox}">
          <Setter Property="Margin" Value="2" />
          <Setter Property="Background" Value="LightGray" />
        </Style>
      </Window.Resources>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="100" />
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition /> <RowDefinition /> <RowDefinition />
          <RowDefinition /> <RowDefinition /> <RowDefinition />
        </Grid.RowDefinitions>
        <Label Grid.Column="0" Grid.Row="0" Content="Bindungsart" />
        <Label Grid.Column="1" Grid.Row="0" Content="Quelle" />
        <Label Grid.Column="2" Grid.Row="0" Content="Ziel" />
        <Label Grid.Column="0" Grid.Row="1" Content="Default" />
        <TextBox Name="txtDefaultQuelle" Text="Test"
                 Grid.Column="1" Grid.Row="1" />
        <TextBox Name="txtDefaultZiel" Grid.Column="2" Grid.Row="1"
                 Text="{Binding ElementName=txtDefaultQuelle,
                 Path=Text}"/>
        <Label Grid.Column="0" Grid.Row="2" Content="OneWay" />
        <TextBox Name="txtOneWayQuelle" Text="Test"
                 Grid.Column="1" Grid.Row="2" />
        <TextBox Name="txtOneWayZiel" Grid.Column="2" Grid.Row="2"
                 Text="{Binding ElementName=txtOneWayQuelle,
                 Path=Text, Mode=OneWay}" />
        <Label Grid.Column="0" Grid.Row="3" Content="TwoWay" />
        <TextBox Name="txtTwoWayQuelle" Text="Test"
                 Grid.Column="1" Grid.Row="3" />
        <TextBox Name="txtTwoWayZiel" Grid.Column="2" Grid.Row="3"
                 Text="{Binding ElementName=txtTwoWayQuelle,
                 Path=Text, Mode=TwoWay}" />
        <Label Grid.Column="0" Grid.Row="4" Content="OneTime" />
        <TextBox Name="txtOneTimeQuelle" Text="Test"
                 Grid.Column="1" Grid.Row="4" />
        <TextBox Name="txtOneTimeZiel" Grid.Column="2" Grid.Row="4"
                 Text="Test" Focusable="False" />
        <Label Grid.Column="0" Grid.Row="5" Content="OneWayToSource" />
        <TextBox Name="txtOneWayToSourceQuelle" Text="Test"
                 Grid.Column="1" Grid.Row="5"/>
        <TextBox Name="txtOneWayToSourceZiel" Grid.Column="2" Grid.Row="5"
                 Text="{Binding ElementName=txtOneWayToSourceQuelle,
                 Path=Text,
                 Mode=OneWayToSource, FallbackValue=Test}" />
      </Grid>
    </Window>
Hier klicken, um das Bild zu vergrößern Abbildung 21.2 Testprogramm zur Bindungsrichtung In der mittleren Spalte des Grid-Steuerelements sind die Textboxen zu finden, die sich an eine Datenquelle binden. Die Datenquelle selbst wird durch die jeweils rechts angeordnete TextBox repräsentiert. Sie können in beiden TextBoxen, also sowohl denjenigen, die als Datenquelle dienen, als auch in denjenigen, die als Datenziel fungieren, Änderungen eintragen und dabei beobachten, wie sich der entsprechende Mitspieler verhält. Beachten Sie dabei aber, dass Sie auch die TextBox verlassen müssen, in der Sie eine Änderung vorgenommen haben. ### 21.2.3 Änderung der Datenquelle Nehmen wir an, wir hätten in unserer Anwendung die Klasse Person wie folgt definiert:
    public class Person {
      // Eigenschaften
      private string _Name;
      private int _Alter;
      public int Alter {
        get { return _Alter; }
        set {
          _Alter = value;
        }
      }
      public string Name {
        get { return _Name; }
        set {
          _Name = value;
        }
      }
      // Konstruktor
      public Person(string name, int alter) {
        Name = name;
        Alter = alter;
      }
    }
Ein Objekt dieser Klasse soll mit den beiden Eigenschaften Name und Alter an je eine TextBox gebunden werden. Das ist mit dem Binding-Objekt nicht weiter schwierig. Wir wollen jetzt aber noch einen Schritt weiter gehen, indem wir die in den TextBoxen angezeigten Eigenschaftswerte automatisch an geänderte Eigenschaftswerte anpassen lassen. Um diese Forderung zu erfüllen, ist die Klasse Person nicht ausreichend implementiert, denn die Steuerelemente müssen auch von einer Änderung in Kenntnis gesetzt werden. Dazu muss Person die Schnittstelle INotifyPropertyChanged implementieren, die zum Namespace System.ComponentModel gehört. Diese Schnittstelle schreibt nur das Ereignis PropertyChanged vor. Sie sollten neben der Definition des Ereignisses die Auslösung des Events durch eine geschützte On-Methode kapseln.
    public class Person : INotifyPropertyChanged {
      public event PropertyChangedEventHandler PropertyChanged;
      protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
        if (PropertyChanged != null)
          PropertyChanged(this, e);
      }
      ...
    }
PropertyChanged wird ausgelöst, wenn sich eine Eigenschaft ändert, also innerhalb des Setters der Eigenschaftsmethode. Sie übergeben dabei ein EventArgs-Objekt, dem Sie über den Konstruktor mitteilen, wie die Bezeichnung der geänderten Eigenschaft lautet.
    public int Alter {
      get { return _Alter; }
      set {
          _Alter = value;
          OnPropertyChanged(new PropertyChangedEventArgs("Alter"));
      }
    }
    public string Name {
      get { return _Name; }
      set {
          _Name = value;
          OnPropertyChanged(new PropertyChangedEventArgs("Name"));
      }
    }
Die Klasse Person ist nun so weit vorbereitet, dass die an ein Objekt dieses Typs gebundenen Steuerelemente Notiz von einer Änderung an Alter oder Name nehmen und den angezeigten Inhalt danach an die neuen Daten in der Quelle anpassen können. Im Beispielprogramm PropertyChangedSample wird die Klasse Person benutzt. Die GUI enthält neben zwei Textboxen, die die beiden Eigenschaften der Person anzeigen, eine Schaltfläche. Im Click-Ereignis der Schaltfläche werden sowohl der Name als auch das Alter geändert. Die Änderungen werden vom WPF-Subsystem an die gebundenen Textboxen weitergeleitet und dort angezeigt. Die Bindung des Person-Objekts erfolgt im Konstruktor des Window durch Zuweisung an die Eigenschaft DataContext.
    // ---------------------------------------------------------
    // Beispiel: ...\Kapitel 21\PropertyChangedSample
    // ---------------------------------------------------------
    <Window ...>
      <StackPanel>
        <TextBox Margin="10" Height="25" Name="txtName"
                 Text="{Binding Path=Name}"></TextBox>
        <TextBox Margin="10" Height="25" Name="txtAlter"
                 Text="{Binding Path=Alter}"></TextBox>
        <Button Height="30" Width="120" Name="btnChangePerson"
                Click="btnChangePerson_Click">Person ändern</Button>
      <StackPanel>
    </Window>
    // in der Code-Datei
    public partial class MainWindow : Window {
      // Datenquelle bereitstellen
      private Person person = new Person("Franz", 67);
      public MainWindow() {
        InitializeComponent();
        this.DataContext = person;
      }
      private void btnChangePerson_Click(object sender, RoutedEventArgs e) {
        person.Name = "Gerald";
        person.Alter = 34;
      }
    }
### 21.2.4 Weitere Möglichkeiten im Zusammenhang mit der Datenbindung Eine der Stärken von WPF sind die Datenbindungsmöglichkeiten. Im Abschnitt zuvor haben Sie nur einen ersten Vorgeschmack bekommen. Damit sind aber noch lange nicht die Grenzen erreicht, die Flexibilität reicht noch deutlich weiter. Das möchte ich Ihnen nun zeigen, wobei ich das Beispielprogramm des letzten Abschnitts um weitere Fähigkeiten erweitern werde. Die bereits vorgestellte Einstellung der Bindungsrichtung der Eigenschaft Mode (siehe auch Tabelle 21.1) gilt natürlich auch in unserem Beispiel. Ergänzen wir im XAML-Code die beiden TextBox-Steuerelemente um Mode=TwoWay, wird die Änderung in einer der Textboxen zwangsläufig dazu führen, dass auch die Objektdaten geändert werden.
    <TextBox Margin="10" Height="25" Name="txtName"
             Text="{Binding Path=Name, Mode=TwoWay}"></TextBox>
    <TextBox Margin="10" Height="25" Name="txtAlter"
             Text="{Binding Path=Alter, Mode=TwoWay}"></TextBox>
Nun stellt sich die Frage, wann die Änderungen wirksam werden. Ganz allgemein lässt sich eine für alle Steuerelemente gültige Aussage nicht machen, da hierbei die Standardeinstellungen unterschiedlich sind: Bei einigen Steuerelementen werden sie wirksam, wenn sich die Eigenschaft ändert, bei anderen hingegen, wenn sie ihren Fokus verlieren. Sie können aber das Verhalten für jedes datengebundene Steuerelement individuell einstellen, indem Sie die Eigenschaft UpdateSourceTrigger auf einen der in Tabelle 21.3 aufgeführten Werte einstellen.

Tabelle 21.3 Einstellungen der Eigenschaft »UpdateSourceTrigger«

Wert | Beschreibung
Default | Die Standardeinstellung variiert von Komponente zu Komponente. Oft ist es PropertyChanged, in selteneren Fällen LostFocus (wie bei einer TextBox).
Explicit | Die Änderung muss explizit durch den Aufruf der UpdateSource-Methode des Zielobjekts erfolgen.
LostFocus | Die Quelle wird aktualisiert, wenn die Zielkomponente den Fokus verliert.
PropertyChanged | Die Aktualisierung erfolgt bei jeder Änderung. Allerdings beansprucht diese Einstellung die Ressourcen sehr intensiv.
Bei einer TextBox sieht die Voreinstellung vor, dass eine Änderung bei Verlust des Fokus zurückgeschrieben wird. Ist dieses Verhalten gewünscht, erübrigt sich für dieses Steuerelement die explizite Angabe von UpdateSourceTrigger. Betrachten wir daher einen anderen Fall, nämlich den der Einstellung Explicit. Hierbei wird der Anstoß zur Aktualisierung der Werte von außen gegeben. Das könnte beispielsweise das Drücken der -Taste sein oder auch das Click-Ereignis einer Schaltfläche. Dazu wird eine zweite Schaltfläche im Window platziert. Im Ereignishandler wird zuerst die Bindung des Steuerelements vom Typ BindingExpression abgerufen. Bereitgestellt wird dieses Objekt von der Methode GetBindingExpression des betreffenden Steuerelements. Als Übergabewert an die Methode dient die datengebundene Eigenschaft des Controls. Auf dem BindingExpression-Objekt ruft man die Methode UpdateSource auf, um die ursprüngliche Datenquelle zu aktualisieren.
    private void btnUpdate_Click(object sender, RoutedEventArgs e) {
      BindingExpression binding =
                 txtName.GetBindingExpression(TextBox.TextProperty);
      binding.UpdateSource();
    }
### 21.2.5 Die Ereignisse »SourceUpdated« und »TargetUpdated« Ändert sich die Datenquelle, wird die datengebundene Komponente durch die Auslösung des Ereignisses PropertyChanged der Schnittstelle INotifyPropertyChanged über die Änderung informiert und kann sich aktualisieren. Der Aufruf der Methode UpdateSource auf das BindingExpression-Objekt des Steuerelements führt zu einer Aktualisierung der eigentlichen Datenquelle. Zwischen diese beiden Aktualisierungen lassen sich auch noch weitere Ereignisse schalten, in denen Sie Code implementieren können, der im Zusammenhang mit der anstehenden Aktualisierung steht. Es handelt sich um die beiden Ereignisse SourceUpdated und TargetUpdated.

Tabelle 21.4 Ereignisse bei der Aktualisierung datengebundener Komponenten

Ereignis | Beschreibung
SourceUpdated | Dieses Ereignis wird ausgelöst, wenn sich das Steuerelement ändert.
TargetUpdated | Dieses Ereignis tritt bei einer Änderung der ursprünglichen Datenquelle auf.
Vielleicht erscheint Ihnen im ersten Moment die Bezeichnung der Ereignisse sinnverdreht. Tatsächlich ist es aber so, dass bei den beiden Ereignissen unter Source die datengebundene Komponente verstanden wird, beispielsweise eine TextBox, und unter Target die ursprüngliche Datenquelle. Natürlich können Sie die Ereignisse nicht nur paarweise nutzen, sondern auch je nach Bedarf nur das eine oder andere. Die beiden Ereignisse werden nicht standardmäßig ausgelöst – sie müssen regelrecht aktiviert werden. Dazu dienen die beiden Eigenschaften NotifyOnSourceUpdated und NotifyOnTargetUpdated, die beide auf True festgelegt werden müssen. Darüber hinaus sind natürlich die Ereignishandler der beiden Ereignisse zu registrieren, die übrigens als »Attached Events« in der Klasse Binding definiert sind.
    <TextBox Binding.SourceUpdated="txt_SourceUpdated"
             Binding.TargetUpdated="txt_TargetUpdated"
             Margin="10" Height="25" Name="txtName"
             Text="{Binding Path=Name,
                    UpdateSourceTrigger=Explicit,
                    NotifyOnTargetUpdated=True,
                    NotifyOnSourceUpdated=True}" />
Hinweis
Sie finden den gesamten Beispielcode zu den Ereignissen SourceUpdated und TargetUpdated auf der Buch-DVD unter Beispiele\Kapitel 21\ SourceUpdated_und_TargetUpdated.