24 Datenbindung
In Kapitel 22 haben wir uns mit den elementarsten Grundzügen der Datenbindung beschäftigt. Das Kapitel diente als Vorbereitung zu Kapitel 23, in dem wir uns mit Styles, Triggern und anderen WPF-Techniken beschäftigt haben, die zumindest auf Grundkenntnisse der WPF-Bindung aufsetzen.
In diesem Kapitel werden wir das Thema der Bindungen tiefer ausleuchten. Dabei werden wir uns mit der Bindung an Klassen und Listen und natürlich auch mit der Bindung an Datenbanken beschäftigen. Das Thema geht aber noch weiter, denn Bindungen verlangen durchaus auch die Validierung von Daten und manchmal auch eine Typkonvertierung. Das alles wird in diesem Kapitel behandelt.
24.1 Bindung benutzerdefinierter Objekte

Nehmen wir an, wir hätten in unserer Anwendung die Klasse Person wie folgt definiert:
public class Person {
private string _Name;
public string Name {
get { return _Name; }
set { _Name = value; }
}
private int? _Alter;
public int? Alter {
get { return _Alter; }
set { _Alter = value; }
}
private string _Adresse;
public string Adresse {
get { return _Adresse; }
set { _Adresse = value; }
}
}
Listing 24.1 Definition der Klasse »Person«
Ein Objekt dieser Klasse soll mit den Eigenschaften Name, Alter und Adresse an je eine TextBox gebunden werden. Um die Klasse noch ein wenig zu würzen, ist Alter ein null-fähiger Integer.
Sie haben zwei Möglichkeiten, ein Objekt vom Typ Person in einer WPF-Anwendung zu erzeugen:
- im XAML-Code
- mittels C#-Code
Beide Möglichkeiten wollen wir uns nun ansehen.
24.1.1 Ein Objekt mit XAML-Code erzeugen und binden

Im XAML-Code können innerhalb der Eigenschaft Objekte beliebiger Klassen erzeugt werden. Voraussetzung ist zunächst einmal, dass der Namespace, zu dem die Klasse gehört, bekannt gegeben wird. Sollte sich die Klasse zudem in einer externen Assembly befinden, ist auch diese zu nennen. Angenommen, die Klasse Person würde in der aktuellen Anwendung zum Namespace SimpleBinding gehören, könnte die Bekanntgabe wie folgt lauten:
<Window ... xmlns:local="clr-namespace:SimpleBinding">
Listing 24.2 Bekanntgabe des Namespaces
Das Namespace-Präfix ist frei wählbar, muss aber innerhalb des Window-Elements eindeutig sein. Hier lautet es local.
Im Resources-Abschnitt kann die Klasse Person nun instanziiert werden. Den Eigenschaften des Objekts kann auch sofort ein Anfangswert übergeben werden. Allerdings sind daran zwei Bedingungen geknüpft:
- Damit die Klasse im XAML-Code instanziiert werden kann, muss sie einen parameterlosen Konstruktor haben.
- Um den Eigenschaften Werte zuzuweisen, müssen diese als Property geprägt sein, d. h., sie müssen einen set- und get-Accessor haben.
Diese Bedingungen werden von der Klasse Person in Listing 24.1 erfüllt, so dass mit dem XAML-Code in Listing 24.3 ein Person-Objekt erzeugt werden kann.
<Window.Resources>
<local:Person x:Key="pers" Adresse="Köln" Name="Hans Glück" />
</Window.Resources>
Listing 24.3 Erzeugen eines Objekts vom Typ »Person« im XAML-Code
Den Eigenschaften Name und Adresse werden bereits zwei Eigenschaftswerte übergeben. Man darf natürlich nicht vergessen, der Ressource mit x:Key einen Identifier anzugeben.
Nun wollen wir die Eigenschaft Name des Objekts an die Eigenschaft Text einer TextBox binden. Zunächst einmal ist dazu ein Binding-Objekt notwendig, das innerhalb einer Markup-Extension erzeugt wird. Die Eigenschaft Source des Binding-Objekts beschreibt bekanntlich die Datenquelle. Hier müssen wir in einer weiteren, verschachtelten Markup-Erweiterung unter Angabe von StaticResource und dem Identifier die Ressource ansprechen und können mit Path an die gewünschte Eigenschaft des Person-Objekts binden.
<TextBox Text="{Binding Source={StaticResource pers}, Path=Name}" />
Listing 24.4 Bindung an die Eigenschaft »Name« des »Person«-Objekts
24.1.2 Ein Objekt mit C#-Code erzeugen und binden

Häufiger werden Sie auf die Fälle treffen, in denen ein Objekt im C#-Code erzeugt wird. Hier kommt die Eigenschaft DataContext ins Spiel, um das Objekt zu binden. In Abschnitt 22.3.3 wurde diese Eigenschaft bereits erwähnt. Noch einmal zur Erinnerung: Über eine Eigenschaft DataContext verfügen alle WPF-Elemente, die von FrameworkElement oder FrameworkContentElement abgeleitet sind. Folglich gehören zu diesen Elementen das Window, das Grid, das StackPanel, aber auch die TextBox.
Angenommen, wir würden mit C#-Programmcode ein Person-Objekt erzeugen und beabsichtigen, dieses mit dem Datenkontext des Window zu verbinden. Dann könnte die Bindung wie in Listing 24.5 gezeigt innerhalb des Konstruktors nach dem Aufruf der Methode InitializeComponent erfolgen.
// Beispiel: ..\Kapitel 24\SimpleBinding
public partial class MainWindow : Window {
Person pers = new Person { Name = "Manfred Fischer",
Alter = null,
Adresse="Köln, Bahnhofstraße" };
public MainWindow() {
InitializeComponent();
this.DataContext = pers;
}
}
Listing 24.5 Bindung an die Eigenschaft »DataContext«
Im XAML-Code erfolgt dann, wie in Listing 24.6 gezeigt, die Bindung an die Eigenschaften des Objekts.
<Window ...>
<Grid>
<Grid.RowDefinitions>
[...]
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
[...]
</Grid.ColumnDefinitions>
<StackPanel Background="Black" Grid.ColumnSpan="2">
<Label>Personendaten</Label>
</StackPanel>
<Label>Name:</Label>
< TextBox Text="{Binding Path=Name}"/>
<Label>Alter:</Label>
<TextBox Text="{Binding Path=Alter}"/>
<Label>Adresse:</Label>
<TextBox Text="{Binding Path=Adresse}"/>
</Grid>
</Window>
Listing 24.6 Bindung eines WPF-Elements an eine »DataContext«-Datenquelle
Abbildung 24.1 Ausgabe des Beispielprogramms »SimpleBinding«
Die Bindung erfolgt, ohne dass explizit die Datenquelle mit den Eigenschaften ElementName oder Source des Binding-Objekts angegeben wird. In diesen Fällen durchsucht WPF die DataContext-Eigenschaften des Elementbaums und fängt dabei beim aktuellen Element an. Wird hier keine Angabe gefunden, wird das in der Hierarchie weiter oben stehende Objekt abgefragt. In unserem Beispiel handelt es sich um das Grid. Hat auch die DataContext-Eigenschaft des Grid den Inhalt null, wird noch im Window gesucht. Hier wird die WPF fündig und bindet die unter Path angegebene Objekteigenschaft an das Steuerelement an.
Die Suche nach der ersten DataContext-Eigenschaft, die nicht null ist, kann man sich zunutze machen. Dieses Verhalten ermöglicht, durchaus auch mehrere Datenquellen an mehrere Elemente zu binden. Durch ein geschicktes Layout lassen sich auf diese Weise in einem Fenster mehrere Bereiche mit unterschiedlichen Datenquellen festlegen.
Binden von »null«-Werten
Das Alter eines Person-Objekts kann per Definition in der entsprechenden Klasse null sein. Damit ähnelt diese Eigenschaft vielen Feldern in Datenbanktabellen, die ebenfalls null zulassen. Die Konsequenz ist, dass in dem Element, das an ein solches Feld gebunden ist, nichts angezeigt wird (siehe Abbildung 24.1).
Die WPF bietet uns eine Möglichkeit einer vordefinierten Anzeige an, die immer dann erscheint, wenn der Feldwert null ist. Dazu gibt man der Eigenschaft TargetNullValue die Zeichenfolge an, die in diesem Fall angezeigt werden soll:
<TextBox Text="{Binding Path=Alter, TargetNullValue=[keine Angabe]}"/>
Listing 24.7 Binden von Feldern, deren Inhalt »null« ist
Natürlich sind die eckigen Klammern nicht unbedingt notwendig. Sie dienen vielmehr dazu, dem Anwender zu signalisieren, dass es sich hier um keinen gültigen Wert aus der Datenquelle handelt.
24.1.3 Aktualisieren benutzerdefinierter Objekte
Wir wollen jetzt aber noch einen Schritt weiter gehen und die Daten des Objekts im Beispielprogramm SimpleBinding ändern. Die Änderung soll zur Laufzeit erfolgen, wenn der Anwender auf eine Schaltfläche klickt, die dem Fenster noch hinzugefügt werden muss.
<Button Name="button1" Click="button1_Click">
Objektdaten ändern
</Button>
Der Code des Ereignishandlers:
private void button1_Click(object sender, RoutedEventArgs e) {
pers.Name = "Hans Frisch";
pers.Alter = 35;
pers.Adresse = "Köln, Hauptstraße";
}
Listing 24.8 Codeergänzung des Beispielprogramms »SimpleBinding«
Mit dieser Ergänzung werden wir aber nicht, wie vielleicht erhofft, die geänderten Person-Daten in den Textboxen sehen. Diese zeigen immer noch die alten und nun auch nicht mehr aktuellen Daten an. Woher sollen die Textboxen auch die Informationen bekommen, dass sich das Objekt geändert hat?
Um dieses Problem zu lösen, bieten sich drei Alternativen an:
- Sie können jede Eigenschaft als Abhängigkeitseigenschaft implementieren. Diese Variante ist mit verhältnismäßig viel Aufwand verbunden und wird bevorzugt bei Objekten benutzt, die eine grafische Präsentation haben. Zudem sollte man auch einen unter Umständen wesentlichen Nachteil der Dependency Properties nicht außer Acht lassen: Beim Multithreading kann es zu Problemen kommen.
- Sie können in jeder Eigenschaft des Objekts ein separates Ereignis bereitstellen. Jedes Ereignis muss einen Bezeichner haben, bei dem zuerst der Eigenschaftsname angegeben ist, gefolgt vom Suffix Changed. In unserem Beispiel würden die drei erforderlichen Events NameChanged, AlterChanged und AdresseChanged lauten.
- Die dritte Möglichkeit ist die Implementierung der Schnittstelle INotifyPropertyChanged. Dieses Interface gehört zum Namespace System.ComponentModel und schreibt das Ereignis PropertyChanged vor, das nach der Eigenschaftsänderung ausgelöst werden soll.
Die letztgenannte Lösung mit dem Interface drängt sich in unserem Beispiel geradezu auf. Daher implementieren wir dieses Interface und kapseln darüber hinaus das Ereignis in der geschützten Methode OnPropertyChanged:
public class Person : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
if (PropertyChanged != null)
PropertyChanged(this, e);
}
[...]
}
Listing 24.9 Implementieren des Interfaces »INotifyPropertyChanged«
Das Ereignis PropertyChanged muss ausgelöst werden, wenn sich eine Eigenschaft ändert, also innerhalb des set-Zweigs der Eigenschaftsmethoden. Dabei teilen Sie dem Konstruktor des PropertyChangedEventArgs-Objekts mit, wie der Name 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"));
}
public string Adresse {
get { return _Adresse; }
set {
_Adresse = value;
OnPropertyChanged(new PropertyChangedEventArgs("Adresse"));
}
}
Listing 24.10 Ergänzung der Klasse »Person«
Jetzt ist die Klasse Person so weit vorbereitet, dass die an ein Objekt dieses Typs gebundenen Steuerelemente Notiz von einer Änderung an Adresse, Alter oder Name nehmen und den angezeigten Inhalt danach an die neuen Daten in der Quelle anpassen können.
Sie finden das komplette Beispielprogramm auf der Buch-DVD unter ...\Kapitel 24\ChangeNotificationSample.
Sie müssen noch nicht einmal in jedem set-Zweig das PropertyChanged-Ereignis auslösen. Es genügt, wenn Sie das Ereignis nur einmal auslösen und dem PropertyChangedEventArgs-Objekt einen Leerstring oder null übergeben. Der WPF wird damit signalisiert, dass sich alle Eigenschaften geändert haben; sie wird die Bindung der Elemente an das Objekt daraufhin neu aufbauen. Wählen Sie dann aber auch die Eigenschaft des Objekts, die als Letztes geändert wird, als diejenige aus, die den Event auslöst. Bezogen auf unseren Click-Ereignishandler in Listing 24.8 wäre das die Eigenschaft Adresse.
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.