22.7 Bindungsarten
Bisher haben wir uns nicht darum gekümmert, nach welchen Regeln die Synchronisation der verbundenen Eigenschaften funktioniert. Mit dem Attribut Mode legen Sie fest, in welchen Richtungen die Bindung aktiv ist. Beispielsweise können Sie die Standardvorgaberichtung von der Quelle zum Ziel auch umkehren. Dann aktualisiert das Ziel die Quelle.
Tabelle 22.2 listet die möglichen Modi vom Typ der Enumeration BindingMode auf.
Bindungstyp | Übertragung des Wertes |
Default |
Der für die Eigenschaft definierte Standard |
OneTime |
Einmalig von der Quelle zum Ziel, danach Kappen der Verbindung |
OneWay |
Von der Quelle zum Ziel. Nach Änderung der Daten im Zielobjekt wird die Verbindung gekappt. |
OneWayToSource |
Von der Quelle zum Ziel. Ändert sich der Wert in der Quelle, bleibt zwar die Bindung bestehen, aber die Daten werden nicht übertragen. |
TwoWay |
Von der Quelle zum Ziel und umgekehrt |
Das folgende Beispielprogramm demonstriert anschaulich die Auswirkung der verschiedenen Modi. Dazu hat das Formular in Abbildung 22.6 für jeden Modus je zwei Textboxen.
Abbildung 22.6 Demonstration der Bindungsarten
'...\WPFKonzepte\Datenbindung\Modus.xaml |
<Window ...> <Window.Resources> <Style TargetType="{x:Type Control}"> <Setter Property="Margin" Value="2" /> </Style> <Style TargetType="{x:Type TextBox}"> <Setter Property="Margin" Value="2" /> </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="DefaultQuelle" Text="Test" Grid.Column="1" Grid.Row="1"/> <TextBox Name="DefaultZiel" Grid.Column="2" Grid.Row="1" Text="{Binding ElementName=DefaultQuelle, Path=Text}"/> <Label Grid.Column="0" Grid.Row="2" Content="OneWay" /> <TextBox Name="OneWayQuelle" Text="Test" Grid.Column="1" Grid.Row="2" /> <TextBox Name="OneWayZiel" Grid.Column="2" Grid.Row="2" Text="{Binding ElementName=OneWayQuelle, Path=Text, Mode=OneWay}" /> <Label Grid.Column="0" Grid.Row="3" Content="TwoWay" /> <TextBox Name="TwoWayQuelle" Text="Test" Grid.Column="1" Grid.Row="3" /> <TextBox Name="TwoWayZiel" Grid.Column="2" Grid.Row="3" Text="{Binding ElementName=TwoWayQuelle, Path=Text, Mode=TwoWay}" /> <Label Grid.Column="0" Grid.Row="4" Content="OneTime" /> <TextBox Name="OneTimeQuelle" Text="Test" Grid.Column="1" Grid.Row="4" /> <TextBox Name="OneTimeZiel" Grid.Column="2" Grid.Row="4" Text="Test" Focusable="False" /> <Label Grid.Column="0" Grid.Row="5" Content="OneWayToSource" /> <TextBox Name="OneWayToSourceQuelle" Text="Test" Grid.Column="1" Grid.Row="5"/> <TextBox Name="OneWayToSourceZiel" Grid.Column="2" Grid.Row="5" Text="{Binding ElementName=OneWayToSourceQuelle, Path=Text, Mode=OneWayToSource, FallbackValue=Test}" /> </Grid> </Window>
Die Eigenschaft Text einer TextBox ist standardmäßig auf TwoWay eingestellt. Änderungen in der Quelle werden sofort vom Ziel angezeigt. Ändern Sie aber den Inhalt im Ziel, müssen Sie erst die TextBox verlassen, ehe die Änderung in der Quelle wirksam wird.
22.7.1 Aktualisierung der Quelle
Die eben beschriebene Verhaltensweise ist nicht immer wünschenswert. Sie lässt sich mit dem Attribut UpdateSourceTrigger vom Typ der gleichnamigen Enumeration steuern (siehe Tabelle 22.3).
Konstante | Beschreibung |
Default |
Komponentenabhängige Standardeinstellung. Oft ist dies PropertyChanged, seltener LostFocus (wie bei einer TextBox). |
Explicit |
Änderung erfolgt nur durch expliziten Aufruf der UpdateSource-Methode des Zielobjekts. |
LostFocus |
Die Quelle wird aktualisiert, wenn das Ziel den Fokus verliert. |
PropertyChanged |
Die Aktualisierung erfolgt bei jeder Änderung (ressourcenintensiv!). |
Um eine Zweiwegeaktualisierung in Echtzeit zu erreichen, geben Sie im Binding-Abschnitt statt Mode das Attribut UpdateSourceTrigger an:
<TextBox Name="TwoWayZiel" Grid.Column="2" Grid.Row="3" Text="{Binding ElementName=TwoWayQuelle, Path=Text, UpdateSourceTrigger=PropertyChanged }" />
Sehen wir uns abschließend noch die Einstellung Explicit an. Hierbei wird der Anstoß zur Aktualisierung der Werte von außen gegeben. Das kann beispielsweise das Drücken der -Taste sein oder auch das Click-Ereignis einer Schaltfläche.
Die beiden folgenden Codefragmente zeigen, wie das Drücken der -Taste in der TextBox namens unten (dem »Ziel«) zum Aktualisieren einer anderen TextBox namens oben (der »Quelle«) führt.
<StackPanel> <TextBox Name="oben" Height="100"></TextBox> <TextBox Name="unten" Height="100" KeyDown="Gedrückt" Text="{Binding ElementName=oben, Path=Text, UpdateSourceTrigger=Explicit}" /> </StackPanel>
Die Bindung erfolgt im XAML-Code. Daher muss im Visual Basic-Code zuerst mit der Methode GetBindingExpression ein BindingExpression-Objekt abgerufen werden, um darauf die Methode UpdateSource aufzurufen.
Private Sub Gedrückt(sender As Object, e As KeyEventArgs) If e.Key = Key.Enter Then _ unten.GetBindingExpression(TextBox.TextProperty).UpdateSource() End Sub
22.7.2 Datenbindungsquellen
Bindung an beliebige Objekte
Angenommen, Sie haben eine Klasse Person implementiert. Objekte dieses Typs sollen als Datenquelle dienen und in einem Window-Objekt eingebunden werden. Damit werden auch Forderungen an die Implementierung der Klasse gestellt:
- Implementierung der Schnittstelle System.ComponentModel.INotifyPropertyChanged
- Definition eines parameterlosen Konstruktors
- Datenquellen sind nur Eigenschaften mit öffentlicher Eigenschaftsmethode, dagegen können öffentliche Felder (Variablen auf Klassenebene) nicht gebunden werden.
Über die Schnittstelle INotifyPropertyChanged wird das Ereignis PropertyChanged eingeführt, das immer dann ausgelöst wird, wenn sich eine bindungsfähige Eigenschaft ändert.
Es folgt nun die Definition der Klasse Person. Bei Änderung der Eigenschaften Zuname oder Alter wird die Methode OnPropertyChanged aufgerufen, die ihrerseits das Ereignis PropertyChanged auslöst.
'...\WPFKonzepte\Datenbindung\Person.xaml |
Imports System.ComponentModel Public Class Person : Implements INotifyPropertyChanged ' Ereignis aus der Schnittstelle INotifiyPropertyChanged Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) _ Implements INotifyPropertyChanged.PropertyChanged Private zu As String Public Property Zuname() As String Get Return zu End Get Set(ByVal value As String) zu = value OnPropertyChanged("Zuname") End Set End Property Private alt As Integer Public Property Alter() As Integer Get Return alt End Get Set(ByVal value As Integer) alt = value OnPropertyChanged("Alter") End Set End Property Protected Sub OnPropertyChanged(ByVal prop As String) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop)) End Sub End Class
Im XAML-Code wird Person aus der aktuellen Anwendung benötigt sowie Typen aus dem Namensraum System. Beide Namensräume werden in der XAML-Datei angegeben.
xmlns:clr="clr-namespace:System;assembly=mscorlib"
xmlns:src="clr-namespace:Datenbindung"
Nun kann die Klasse Person auch in der XAML-Datei verwendet werden. Um Person in der XAML-Datei zu instanziieren, verwendet man den Ressourcenabschnitt. Optional können den Eigenschaften sofort Werte zugewiesen werden.
<Window.Resources> <src:Person x:Key="pers1" Zuname="Schmidt" Alter="43" /> </Window.Resources>
Danach können Sie die Möglichkeiten der Datenbindung benutzen. In unserem Beispiel werden Zuname und Alter der Person in zwei Textboxen angezeigt.
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50" /><ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="25" /><RowDefinition Height="25" /> </Grid.RowDefinitions> <Label Grid.Column="0" Grid.Row="0">Zuname:</Label> <TextBox Grid.Column="1" Grid.Row="0" Name="txtZuname" Text="{Binding Source={StaticResource pers1}, Path=Zuname}" /> <Label Grid.Column="0" Grid.Row="1">Alter:</Label> <TextBox Grid.Column="1" Grid.Row="1" Name="txtAlter" Text="{Binding Source={StaticResource pers1}, Path=Alter}" /> </Grid>
Eine alternative Variante der Datenbindung bietet sich an, wenn das Objekt zahlreiche Eigenschaften hat und mehrere Komponenten an diese Eigenschaften gebunden werden. Weisen Sie dazu der Eigenschaft DataContext des Containers die Ressource zu. Wird in einem untergeordneten Element keine Datenquelle angegeben, sucht das Element in der Hierarchie aufwärts nach einer DataContext-Spezifikation.
<Grid DataContext="{StaticResource pers1}"> ... <Label Grid.Column="0" Grid.Row="0">Zuname:</Label> <TextBox Grid.Column="1" Grid.Row="0" Name="txtZuname" Text="{Binding Path=Zuname}" /> <Label Grid.Column="0" Grid.Row="1">Alter:</Label> <TextBox Grid.Column="1" Grid.Row="1" Name="txtAlter" Text="{Binding Path=Alter}" /> </Grid>
Zugriff auf eine Instanz im XAML-Code
Wollen Sie im Visual Basic-Code auf ein im XAML-Code erzeugtes Objekt zugreifen, zum Beispiel um den Wert einer Eigenschaft zu ändern, dann müssen Sie sich zuerst die Referenz auf das im XAML-Code erzeugte Objekt besorgen. Hier hilft die Methode FindResource der Klasse FrameworkElement weiter, einer Basisklasse von Window. Der Methode wird als Argument das im XAML-Code erzeugte Objekt übergeben. Der Rückgabewert muss noch in den richtigen Typ konvertiert werden.
Private Sub Ändern(sender As Object, e As RoutedEventArgs) Dim pers As Person = CType(FindResource("pers"), Person) MessageBox.Show("Name und Alter: " & pers.Zuname & ", " & pers.Alter) pers.Zuname = "Schmidt-Gerhards" End Sub
Umgekehrt können Sie im Code ein Objekt erzeugen und an DataContext übergeben.
Private Sub Erzeugen(sender As Object, e As RoutedEventArgs) Dim pers As New Person() pers.Zuname = "Fischer" pers.Alter = 55 grid1.DataContext = pers End Sub
Der passende XAML-Code dazu sieht so aus:
'...\WPFKonzepte\Datenbindung\Instanz.xaml |
<Window xmlns:src="clr-namespace:Datenbindung" ...> ... <Window.Resources> <src:Person x:Key="pers1" Zuname="Schmidt" Alter="43" /> </Window.Resources> <Grid Name="grid1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="50" /><ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/><RowDefinition/><RowDefinition/><RowDefinition/> </Grid.RowDefinitions> <Label Grid.Column="0" Grid.Row="0">Zuname:</Label> <TextBox Grid.Column="1" Grid.Row="0" Name="txtZuname" Text="{Binding Path=Zuname}" /> <Label Grid.Column="0" Grid.Row="1">Alter:</Label> <TextBox Grid.Column="1" Grid.Row="1" Name="txtAlter" Text="{Binding Path=Alter}" /> <Button Grid.Column="1" Grid.Row="2" Name="button1" Width="100" HorizontalAlignment="Right" Click="Erzeugen">Datenerzeugung</Button> <Button Grid.Column="1" Grid.Row="3" Name="button2" Width="100" HorizontalAlignment="Right" Click="Ändern">Datenzugriff</Button> </Grid> </Window>
22.7.3 Auflistungen anbinden
Analog zu den im vorigen Abschnitt beschriebenen Objekten müssen Auflistungen die Schnittstelle INotifyCollectionChanged implementieren. Am einfachsten leiten Sie die generische Klasse System.Collections.ObjectModel.ObservableCollection ab, die die Schnittstelle bereits implementiert.
Wir definieren eine Auflistung, die Person-Objekte des vorigen Abschnitts verwaltet. Alle Personen der Auflistung werden in einer ListBox angezeigt (siehe Abbildung 22.7). Da die Auflistung sofort bei der Instanziierung mit Person-Objekten gefüllt werden soll, leiten wir ObservableCollection ab und erzeugen im Konstruktor Person-Objekte.
Abbildung 22.7 Beispiel für Datenschablonen
'...\WPFKonzepte\Datenbindung\Personenliste.xaml |
Imports System.Collections.ObjectModel Public Class Personenliste : Inherits ObservableCollection(Of Person) Public Sub New() Add(New Person() With {.Zuname = "Fischer", .Alter = 52}) Add(New Person() With {.Zuname = "Müller", .Alter = 32}) Add(New Person() With {.Zuname = "Schmitz", .Alter = 85}) Add(New Person() With {.Zuname = "Meier", .Alter = 13}) Add(New Person() With {.Zuname = "Mayer", .Alter = 30}) End Sub End Class
In XAML füllen wir die ListBox mittels einer Schablone. Sie definiert eine Zeile, die statt konkreter Daten eine Bindung zur darzustellenden Eigenschaft benutzt. Die Gestaltung der Zeile erfolgt genau so, als stünde sie direkt im Dokument. Eine solche Schablone lässt sich später der ListBox (oder beispielsweise auch einer ComboBox) zuordnen.
DataTemplate beschreibt eine solche Schablone. Sie wird im Ressourcenbereich der XAML-Datei definiert, in dem zudem ein Objekt vom Typ Personenliste erzeugt wird:
<Window.Resources> <pers:Personenliste x:Key="Persliste" /> <DataTemplate x:Key="ItemTemplateName"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Zuname}" /> <TextBlock Text=" – " /> <TextBlock Text="{Binding Path=Alter}" /> </StackPanel> </DataTemplate> </Window.Resources>
Was bleibt, ist die Zuordnung der Ressourcen zu der ListBox. In ItemSource wird die Datenquelle genannt, aus der die Informationen zu beziehen sind. ItemTemplate beschreibt das Muster (DataTemplate), mit dem die in ItemSource spezifizierten Daten angezeigt werden.
<ListBox Name="listbox1" Height="80" ItemsSource="{Binding Source={StaticResource Persliste}}" ItemTemplate="{StaticResource ItemTemplateName}" />
Das Fenster enthält noch zwei Buttons. Beim Klicken auf den ersten Button wird das aktuell selektierte Listboxelement aus der Liste gelöscht, mit dem zweiten Button wird ein neues Element hinzugefügt. Hier sehen Sie zunächst den kompletten Code der XAML-Datei:
<Window xmlns:pers="clr-namespace:Datenbindung" ...> <Window.Resources> <pers:Personenliste x:Key="Persliste" /> <DataTemplate x:Key="ItemTemplateName"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Zuname}" /> <TextBlock Text=" – " /> <TextBlock Text="{Binding Path=Alter}" /> </StackPanel> </DataTemplate> </Window.Resources> <StackPanel> <ListBox Name="listbox1" Height="80" ItemsSource="{Binding Source={StaticResource Persliste}}" ItemTemplate="{StaticResource ItemTemplateName}" /> <StackPanel Orientation="Horizontal"> <Button Name="btnDelete" Click="Löschen">Person löschen</Button> <Button Name="btnAdd" Click="Hinzufügen">Person hinzufügen</Button> </StackPanel> </StackPanel> </Window>
Was noch fehlt, ist der Code in den Ereignishandlern der beiden Schaltflächen:
Partial Public Class Liste Public liste As Personenliste Private Sub Laden(ByVal sender As Object, ByVal e As RoutedEventArgs) liste = CType(FindResource("Persliste"), Personenliste) End Sub Private Sub Löschen(ByVal sender As Object, ByVal e As RoutedEventArgs) liste.RemoveAt(listbox1.SelectedIndex) End Sub Private Sub Hinzufügen(ByVal sender As Object, ByVal e As RoutedEventArgs) liste.Add(New Person() With {.Zuname = "Kleinen", .Alter = 45}) End Sub End Class
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.