Zum Hauptinhalt springen

Databinding

Einführung

Data Binding in WPF ermöglicht die automatische Synchronisierung von Daten zwischen einer Datenquelle und einer Zielkomponente. Dabei wird eine Verbindung zwischen den Eigenschaften der Datenquelle und den Eigenschaften der Zielkomponente hergestellt, sodass Änderungen an einer Eigenschaft automatisch in der anderen Eigenschaft reflektiert werden.

<ListBox x:Name="userListBox" ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}" />
<TextBox Text="{Binding SelectedUser.Name}" />

In diesem Beispiel wird die ListBox an die Liste der Benutzer (Users) gebunden und die SelectedItem-Eigenschaft an die ausgewählte Benutzerinstanz (SelectedUser) im ViewModel. Die TextBox wird dann an die Name-Eigenschaft des ausgewählten Benutzers gebunden.

Durch diese Data Bindings wird die ListBox automatisch aktualisiert, wenn sich die Liste der Benutzer ändert, und die TextBox zeigt automatisch den Namen des ausgewählten Benutzers an, wenn sich die Auswahl ändert.

Data Binding ermöglicht eine effiziente und deklarative Möglichkeit, Daten zwischen den verschiedenen Elementen einer WPF-Anwendung zu synchronisieren und die Benutzeroberfläche dynamisch an die zugrunde liegenden Daten anzupassen.

Es kann auch eine längere Schreibweise in XAML verwendet werden. In der längeren Schreibweise werden die Data Bindings explizit über die Binding-Klasse definiert. Die Path-Eigenschaft gibt den Pfad zur jeweiligen Datenquelle an, und der Mode wird auf "TwoWay" gesetzt, um die Änderungen in beiden Richtungen zu ermöglichen.

<ListBox x:Name="userListBox">
<ListBox.ItemsSource>
<Binding Path="Users" />
</ListBox.ItemsSource>
<ListBox.SelectedItem>
<Binding Path="SelectedUser" Mode="TwoWay" />
</ListBox.SelectedItem>
</ListBox>

<TextBox>
<TextBox.Text>
<Binding Path="SelectedUser.Name" Mode="TwoWay" />
</TextBox.Text>
</TextBox>

Die längere Schreibweise erfordert mehr Zeilen Code, bietet jedoch mehr Flexibilität und Kontrolle über die Data Bindings in der XAML-Datei.

Natürlich kann das Binding auch im C# Code erstellt werden.

ListBox userListBox = new ListBox();
userListBox.SetBinding(ListBox.ItemsSourceProperty, new Binding("Users"));
userListBox.SetBinding(ListBox.SelectedItemProperty, new Binding("SelectedUser") { Mode = BindingMode.TwoWay });

TextBox userTextBox = new TextBox();
userTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedUser.Name") { Mode = BindingMode.TwoWay });

Eigenschaften der Klasse Binding

EigenschaftBeschreibung
PathDer Pfad zur Eigenschaft, die im Datenziel gebunden werden soll
SourceDas Quellobjekt, von dem die Daten gelesen werden sollen
ModeDer Bindungsmodus (OneWay, TwoWay, OneTime, etc.)
UpdateSourceTriggerDer Trigger, der das Aktualisieren des Quellwerts auslöst
ConverterEin Konverter, der den Wert während der Bindung transformiert
ConverterParameterEin Parameter, der dem Konverter übergeben wird
ValidatesOnDataErrorsGibt an, ob Fehler beim Validieren der Daten angezeigt werden sollen
ValidatesOnExceptionsGibt an, ob Ausnahmen beim Validieren der Daten angezeigt werden sollen
NotifyOnSourceUpdatedGibt an, ob eine Benachrichtigung ausgelöst wird, wenn sich der Quellwert aktualisiert
NotifyOnTargetUpdatedGibt an, ob eine Benachrichtigung ausgelöst wird, wenn sich der Zielwert aktualisiert
NotifyOnValidationErrorGibt an, ob eine Benachrichtigung ausgelöst wird, wenn ein Validierungsfehler auftritt
UpdateSourceExceptionFilterEin Rückruf, um benutzerdefinierte Logik für die Behandlung von UpdateSourceException zu implementieren
StringFormatEin Formatierungsmuster für die Darstellung des gebundenen Werts als Zeichenkette

Beispiele

Binden einer Property an eine Textbox

public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
<Window x:Class="de.devcodemonkey.WPFBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.WPFBinding"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:Person x:Key="person" FirstName="David"/>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding Source={StaticResource person}, Path=FirstName}"/>
<Button Content="Show Firstname" Click="Button_Click"/>
</StackPanel>
</Window>

Selfbinding

<Slider ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Value}"/>

DataContext

Im folgendem Beispiel wird das "DataContext" Attribut der Ressource "person" zugewiesen, um den DataContext des Windows an das Person-Objekt zu binden. Dadurch haben Sie direkten Zugriff auf die Eigenschaften von "person" in den Bindings, ohne die "Source" Angabe verwenden zu müssen.

<Window x:Class="de.devcodemonkey.WPFBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.WPFBinding"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
DataContext="{StaticResource person}">
<Window.Resources>
<local:Person x:Key="person" FirstName="David"/>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding Path=FirstName}"/>
<Button Content="Show Firstname" Click="Button_Click"/>
<Slider ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Value}"/>

<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel, AncestorLevel=1}, Path=Value, FallbackValue=Element nicht gefunden}"/>
<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}, Path=Title, FallbackValue=Element nicht gefunden}"/>
</StackPanel>
</Window>

Binding Modi

Binding-Modi definieren das Verhalten von Datenbindungen in XAML und können festlegen, wie sich die Werte zwischen Quell- und Zielobjekten synchronisieren.

Binding-ModusBeschreibung
DefaultVerwendet den Standard-Binding-Modus, der je nach Kontext variieren kann. In den meisten Fällen entspricht dies dem OneWay-Modus.
TwoWayDie Daten werden sowohl vom Quellobjekt zum Zielobjekt als auch vom Zielobjekt zum Quellobjekt übertragen. Änderungen am Quell- oder Zielobjekt werden synchronisiert.
OneTimeDie Daten werden einmalig vom Quellobjekt zum Zielobjekt übertragen. Änderungen am Quellobjekt haben keine Auswirkungen auf das Zielobjekt.
OneWayDie Daten werden nur vom Quellobjekt zum Zielobjekt übertragen. Änderungen am Zielobjekt haben keine Auswirkungen auf das Quellobjekt.
OneWayToSourceDie Daten werden nur vom Zielobjekt zum Quellobjekt übertragen. Änderungen am Zielobjekt haben Auswirkungen auf das Quellobjekt.

UpdateSourceTrigger

UpdateSourceTrigger gibt an, wann der Wert einer Datenbindung vom Zielobjekt auf das Quellobjekt aktualisiert werden soll.

UpdateSourceTrigger-ModusBeschreibung
DefaultVerwendet den Standard-UpdateSourceTrigger-Modus der Bindung
PropertyChangedAktualisiert den Quellwert bei jeder Eigenschaftsänderung im Zielobjekt
LostFocusAktualisiert den Quellwert, wenn das Zielobjekt den Fokus verliert
ExplicitErfordert eine explizite Aktualisierung des Quellwerts durch den Entwickler

Beispiel zu Testen der Modis

<Window x:Class="de.devcodemonkey.BindingModi.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.BindingModi"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Label>DataSource</Label>
<TextBox Name="txt_DataSource">Hello world</TextBox>
<Label>DataDestination</Label>
<TextBox Text="{Binding Text, ElementName=txt_DataSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</StackPanel>
</Window>

Ereignisse SourceUpdated und TargetUpdated

Die Ereignisse SourceUpdated und TargetUpdated treten auf, wenn sich die Werte einer Datenbindung zwischen Quell- und Zielobjekt ändern.

  • Das SourceUpdated-Ereignis tritt auf, wenn der Wert des Quellobjekts in das Zielobjekt aktualisiert wurde.
  • Das TargetUpdated-Ereignis tritt auf, wenn der Wert des Zielobjekts vom Quellobjekt aktualisiert wurde.
private void SourceUpdatedHandler(object sender, DataTransferEventArgs e)
{
Console.WriteLine("SourceUpdated event occurred.");
}

private void TargetUpdatedHandler(object sender, DataTransferEventArgs e)
{
Console.WriteLine("TargetUpdated event occurred.");
}
<TextBox Text="{Binding MyProperty, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" SourceUpdated="SourceUpdatedHandler" TargetUpdated="TargetUpdatedHandler" />

Konverter

Ein Converter in WPF ermöglicht es, Werte zwischen verschiedenen Datentypen zu konvertieren oder zu transformieren. Hier sind einige typische Anwendungsfälle für einen Converter:

  1. Datenformatierung: Ein Converter kann verwendet werden, um Daten in einem bestimmten Format anzuzeigen, z.B. eine Zahl als Währungsbetrag oder ein Datum im gewünschten Datumsformat.
  2. Wertebereichsüberprüfung: Ein Converter kann verwendet werden, um sicherzustellen, dass ein Wert innerhalb eines bestimmten Wertebereichs liegt, und gegebenenfalls eine entsprechende Rückmeldung anzeigen.
  3. Datenanpassung: Ein Converter kann verwendet werden, um Daten aus einer Datenquelle an die Anforderungen eines Steuerelements anzupassen, z.B. einen Boolean-Wert in ein Sichtbarkeitsattribut umwandeln.
  4. Einheitenumrechnung: Ein Converter kann verwendet werden, um Werte zwischen verschiedenen Einheiten umzurechnen, z.B. von Metern in Zentimeter oder von Grad Celsius in Fahrenheit.

Beispiel

public class StringLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string str)
{
return str.Length;
}
return 0;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window x:Class="de.devcodemonkey.Converter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.Converter"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:StringLengthConverter x:Key="StringLengthConverter" />
</Window.Resources>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<Label Content="Number of Words: "/>
<Label Content="{Binding Text, ElementName=MyText, Converter={StaticResource StringLengthConverter},UpdateSourceTrigger=PropertyChanged}"/>
</StatusBar>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Name="MyText" VerticalAlignment="Stretch" AcceptsReturn="True" />
</Grid>
</DockPanel>
</Window>

Validieren von Bindungen

Es gibt folgenden Möglichkeiten eine Validierung zu prüfen:

  • mit einem ExceptionValidationRule Objekt
  • durch Ableitung von der Klasse ValidationRule
  • durch implementieren des Interfaces IDataErrorInfo

Exception Validation Rule

Die ExceptionValidationRule ist eine vordefinierte Validierungsregel in WPF, die verwendet werden kann, um Eingabewerte auf eine Ausnahme zu überprüfen. Sie wird normalerweise in Kombination mit einer Konvertierung oder Formatierung verwendet, um sicherzustellen, dass der eingegebene Wert gültig ist und keine Ausnahme ausgelöst wird.

Wenn eine Ausnahme während der Konvertierung oder Formatierung des Eingabewerts auftritt, wird die ExceptionValidationRule angewendet und ein Validierungsfehler wird ausgelöst. Dadurch wird verhindert, dass der ungültige Wert im Bindungsziel gespeichert wird.

Beispiel

Bei fehlerhafter Eingabe wird ein roter Rahmen um die Textbox dargestellt. Wichtig ist dabei das die Anwendung nicht im Debug Modus ausgeführt wird.

 public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
private int _Age;

public int Age
{
get { return _Age; }
set
{
if (value >= 0)
_Age = value;
else
throw new ArgumentException("The can' be negative.");
}
}
}
<Window x:Class="de.devcodemonkey.ExceptionValidationRule.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.ExceptionValidationRule"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:Person x:Key="person"/>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding Source={StaticResource person}, Path=Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>
</StackPanel>
</Window>

Custom Validation Rule

Eine benutzerdefinierte ValidationRule in WPF ist eine Möglichkeit, benutzerdefinierte Validierungslogik zu implementieren, um die Eingabe von Benutzern in einem WPF-Anwendungsformular zu validieren. Sie ermöglicht es Ihnen, spezifische Validierungsregeln festzulegen und individuelle Fehlermeldungen zu generieren.

Eine benutzerdefinierte ValidationRule wird erstellt, indem eine Klasse abgeleitet wird von der abstrakten Klasse ValidationRule. In dieser Klasse können Sie die Validierungslogik implementieren, indem Sie die Validate-Methode überschreiben. Diese Methode wird aufgerufen, wenn die Validierung für ein bestimmtes Eingabeelement ausgeführt wird.

In der Validate-Methode haben Sie Zugriff auf den Wert des Eingabeelements, den Sie überprüfen möchten. Sie können Ihre benutzerdefinierten Validierungsregeln auf diesen Wert anwenden und ein ValidationResult-Objekt zurückgeben, das den Validierungsergebnisstatus und eine optionale Fehlermeldung enthält.

Um eine benutzerdefinierte ValidationRule in einer XAML-Datei zu verwenden, können Sie die ValidationRules-Eigenschaft des Binding-Objekts für das Eingabeelement festlegen. Dabei weisen Sie der ValidationRules-Eigenschaft eine Instanz Ihrer benutzerdefinierten ValidationRule-Klasse zu.

Mit einer benutzerdefinierten ValidationRule können Sie verschiedene Arten von Validierungen implementieren, wie z.B. Überprüfen auf bestimmte Werte, Überprüfen auf Muster oder Überprüfen auf Geschäftslogikregeln. Sie bietet Flexibilität und ermöglicht es Ihnen, spezifische Validierungsanforderungen in Ihrer WPF-Anwendung umzusetzen.

Beispiel

Bei fehlerhafter Eingabe wird im Label ein entsprechender Fehler ausgegeben.

public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
class AgeValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string valueString = value as string;
if (valueString != null)
{
if (int.TryParse(valueString, out int val))
{
if (val >= 0)
return ValidationResult.ValidResult;
// or return new ValidationResult(true, null);
else
return new ValidationResult(false, "Age is negative");
}
else
return new ValidationResult(false, "Not a number");
}
else
return new ValidationResult(false, "General error");
}
}
<Window x:Class="de.devcodemonkey.CustomValidationRule.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.CustomValidationRule"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:Person x:Key="person"/>
</Window.Resources>
<StackPanel>
<TextBox Name="txt_Age">
<Binding Source="{StaticResource person}" Path="Age" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:AgeValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
<Label Content="{Binding ElementName=txt_Age, Path=(Validation.Errors)[0].ErrorContent}" />
<TextBox></TextBox>
</StackPanel>
</Window>

Validieren mit Interface IDataErrorInfo

IDataErrorInfo ist ein Interface in .NET für die Validierung von Datenobjekten in WPF-Anwendungen. Es ermöglicht die Rückgabe von Fehlermeldungen für ungültige Werte über die Error- und Item-Eigenschaften. Das Error Item wird in Verbindung mit der WPF nicht genutzt Durch Implementierung von IDataErrorInfo können Sie Validierungslogik definieren und Fehlermeldungen für Eigenschaften zurückgeben.

Beispiel

public class Person : IDataErrorInfo
{
public string this[string columnName]
{
get
{
if (columnName.Equals("Age") && Age < 0)
return "Age is negative";
return null;
}
}

public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }

public string Error => throw new NotImplementedException();
}
<Window x:Class="de.devcodemonkey.BindingIDataErrorInfo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.BindingIDataErrorInfo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:Person x:Key="person"/>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding Source={StaticResource person}, Path=Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
</StackPanel>
</Window>

Fehlermeldungen individualisiert Anzeigen

Im folgenden ein Beispiel, wie eine Fehlermeldung individualisiert angezeigt werden kann.

Individualisierter Fehler

<Window x:Class="de.devcodemonkey.BindingIDataErrorInfo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.BindingIDataErrorInfo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:Person x:Key="person"/>
<Style TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<TextBlock Text="{Binding [0].ErrorContent}"
DockPanel.Dock="Right"
Foreground="Red"
Margin="5,0,0,0"
VerticalAlignment="Center" />
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder Name="adorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Margin="20" Width="300" Text="{Binding Source={StaticResource person}, Path=Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
</StackPanel>
</Window>

Datenklassen aktualisieren 👍

Objekte im XAML erzeugen

Um Objekte in XAML zu erzeugen, müssen bestimmte Voraussetzungen erfüllt sein:

  1. Objektklasse: Die Klasse des zu erzeugenden Objekts muss als öffentlich (public) deklariert sein und einen öffentlichen Standardkonstruktor haben. Der Standardkonstruktor ist ein Parameterloser Konstruktor, der verwendet wird, um das Objekt zu instanziieren.

  2. Namespace-Deklaration: Der Namespace, in dem die Objektklasse definiert ist, muss in der XAML-Datei deklariert werden. Dies erfolgt durch die Verwendung der xmlns-Deklaration am Anfang der XAML-Datei.

  3. Assembly-Referenz: Wenn sich die Objektklasse in einer anderen Assembly befindet als der XAML-Code, muss eine Assembly-Referenz angegeben werden. Dies erfolgt durch die Verwendung des assembly-Attributs in der xmlns-Deklaration.

  4. Richtiges Markup: Das Objekt kann dann innerhalb der XAML-Datei erstellt werden, indem es als Ressource oder als Teil eines anderen XAML-Elements definiert wird. Das richtige Markup hängt von der Art des zu erzeugenden Objekts ab (z. B. <ElementName ... /> für Elemente, <x:Key="Key" ... /> für Ressourcen usw.).

public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
<Window.Resources>
<local:Person x:Key="p1" FirstName="David" LastName="Höll"/>
</Window.Resources>

<TextBox Text="{Binding FirstName, Source={StaticResource p1}}"/>

Objekt binden in C#-Code mit DataContext

Häufig wird für die Bindung über den C#-Code DataContext verwendet.

public partial class PersonOverview : Window
{
Person p1 = new Person
{
Id = 1,
FirstName = "David",
LastName = "Höll",
};
public PersonOverview()
{
InitializeComponent();
DataContext = p1;
}
}
<TextBox Text="{Binding LastName}"/>

Aktualisieren von gebundenen Objekten

Standardmäßig werden gebundene Objekte nicht automatisch aktualisiert. Hierzu kann aber die allgemeine Klasse PropertyObservable helfen.

public class PropertyObservable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyname = null)
{
if (Equals(storage, value)) return false;
storage = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
return true;
}
}

Diese kann entsprechen in der Personenklassen beim setter in der Properties verwendet werden.

public class Person : PropertyObservable
{
private string _FirstName;
private string _LastName;

public int Id { get; set; }
public string FirstName
{
get => _FirstName;
set => SetProperty(ref _FirstName, value);
}
public string LastName
{
get => _LastName;
set => SetProperty(ref _LastName, value);
}
}

Werden nun die Eigenschaften des Personen Objektes geändert, werden auch die gebundene Xaml Elemente geändert.

Änderungen könnten wie folgt passieren:

p1.FirstName = "Max";
p1.LastName = "Mustermann";

Binden von Listen 👍

Häufig werden in der WPF auch Listen an Steuerelemente gebunden. Alle diese Steuerlisten erben vom ItemsControl-Steuerelement. An diese Steuerelemente kann über die ItemsSource-Eigenschaft eine Liste als Datenquelle verwendet werden.

Häufig Anwendung finden z.B. die folgenden Elemente:

KlasseBeschreibung
ListBoxEine Liste von Elementen, aus denen der Benutzer ein oder mehrere Elemente auswählen kann.
ListViewEine Liste von Elementen, die in einer tabellarischen Ansicht angezeigt werden können.
ComboBoxEin Dropdown-Menü, das dem Benutzer eine Auswahl aus einer Liste von Elementen ermöglicht.
TabControlEin Steuerelement mit Registerkarten, das den Benutzern ermöglicht, zwischen verschiedenen Ansichten zu wechseln.
TreeViewEine hierarchische Darstellung von Elementen, die in einer Baumstruktur angeordnet sind.
MenuEine Menüleiste mit Dropdown-Menüs, die dem Benutzer den Zugriff auf verschiedene Befehle und Funktionen ermöglicht.
ToolBarEine Leiste mit Symbolen oder Schaltflächen, die dem Benutzer den schnellen Zugriff auf Befehle und Funktionen ermöglicht.
StatusBarEine Leiste am unteren Rand des Fensters, die Informationen oder den Status der Anwendung anzeigt.
DataGridEine tabellarische Darstellung von Daten, die dem Benutzer ermöglicht, Zeilen zu bearbeiten, zu sortieren und zu filtern.
CarouselEine horizontale oder vertikale Sammlung von Elementen, die in einer Endlosschleife durchlaufen werden können.

Binding einfacher Listen und PropertyObservable

Detail View

Im Beispielprogramm wird über die Auswahl des Nachnamens Details einer Person angezeigt. Die Personen werden in jeweils in der Klasse PersonDetailView gespeichert. Damit bei einer Änderung des Nachnamens dieser auch in der Liste aktualisiert wird, wird erneut die SetProperty-Methode der PropertyObservable-Klasse verwendet.

public class PersonDetailView : PropertyObservable
{
private string _LastName;

public string FirstName { get; set; }
public string LastName
{
get => _LastName;
set => SetProperty(ref _LastName, value);
}
public int Age { get; set; }
public string City { get; set; }
}

Code-Behind Code der XAML Datei

In der Code-Behind Datei wird im Konstruktor der ListBox über die ItemSource-Property die Liste zugewiesen. Voraussetzung für das Binden ist, dass diese bei der Zuweisung leer ist. Im weiteren werden der Liste dann noch einige Personen über die Methode createPersons() zugewiesen. Am Ende wird noch das erste Element der Liste ausgewählt und darauf der Fokus gesetzt. Es empfiehlt sich das DataTemplate für die Liste im Windows Resource-Bereich zu definieren und über x:Key und ItemTemplate der ListBox zuzuweisen. Ansonsten ist Voraussetzung, dass Personen Liste zu Beginn leer ist.

public partial class DetailView : Window
{
List<PersonDetailView> personList = new List<PersonDetailView>{
new PersonDetailView { FirstName = "John", LastName = "Doe", Age = 25, City = "New York" },
new PersonDetailView { FirstName = "Jane", LastName = "Smith", Age = 30, City = "London" },
new PersonDetailView { FirstName = "Michael", LastName = "Johnson", Age = 35, City = "Berlin" },
new PersonDetailView { FirstName = "Emily", LastName = "Davis", Age = 28, City = "Paris" },
new PersonDetailView { FirstName = "Daniel", LastName = "Wilson", Age = 32, City = "Tokyo" },
new PersonDetailView { FirstName = "Sophia", LastName = "Taylor", Age = 29, City = "Sydney" },
new PersonDetailView { FirstName = "David", LastName = "Brown", Age = 27, City = "Toronto" },
new PersonDetailView { FirstName = "Olivia", LastName = "Miller", Age = 31, City = "Madrid" },
new PersonDetailView { FirstName = "Matthew", LastName = "Anderson", Age = 33, City = "Rome" },
new PersonDetailView { FirstName = "Emma", LastName = "Martinez", Age = 26, City = "Seoul" }
};

public DetailView()
{
InitializeComponent();
listbox_Persons.ItemsSource = personList;
listbox_Persons.SelectedIndex = 0;
listbox_Persons.Focus();
}
}

XAML Code

Über den folgenden Code wird im XAML Code die ListBox gebunden:

<Window.Resources>
<DataTemplate x:Key="ListBoxItems">
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</Window.Resources>

<ListBox Margin="5" x:Name="listbox_Persons" ItemTemplate="{StaticResource ListBoxItems}"/>

Im weiteren werden noch die Textboxen gebunden über:

<StackPanel Grid.Column="2" Margin="5" DataContext="{Binding SelectedItem, ElementName=listbox_Persons}">
<TextBox Text="{Binding FirstName}"/>
<TextBox Text="{Binding LastName}"/>
<TextBox Text="{Binding Age}"/>
<TextBox Text="{Binding City}"/>
</StackPanel>

Klasse ObservableCollection

Mit der ObservableCollection<T> können dynamische Auflistung erstellt werden, die automatisch Ereignisse auslöst, wenn sich die Auflistung ändert. Sie ermöglicht das Hinzufügen, Entfernen und Ändern von Elementen und ist für die Datenbindung in UI-Steuerelementen geeignet.

Im Beispiel fehlt noch ein Button zum Hinzufügen und entfernen von Personen. Dieses wird wie folgt realisiert.

DetailView mit Buttons

private void btnAdd_Click(object sender, RoutedEventArgs e)
{
PersonDetailView personDetailView = new PersonDetailView();
personList.Add(personDetailView);
listbox_Persons.SelectedItem = personDetailView;
}
 private void btnDelete_Click(object sender, RoutedEventArgs e)
{
PersonDetailView personDetailView = (PersonDetailView)listbox_Persons.SelectedItem;
if (personDetailView != null)
{
personList.Remove(personDetailView);
}
}

kompletter Beispielcode

Hier noch der komplette Code der XAML-Datei sowie Code-Behind-Datei.

<Window x:Class="de.devcodemonkey.WPFBinding.DetailView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:de.devcodemonkey.WPFBinding"
mc:Ignorable="d"
Title="DetailView" Height="450" Width="800">
<Window.Resources>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="3 6 6 3" />
</Style>
<Style TargetType="StackPanel">
<Setter Property="Margin" Value="5"/>
</Style>
<Style TargetType="Button">
<Setter Property="Margin" Value="5"/>
<Setter Property="Width" Value="100"/>
</Style>
<DataTemplate x:Key="ListBoxItems">
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel>
<ListBox Margin="5" x:Name="listbox_Persons" ItemTemplate="{StaticResource ListBoxItems}"/>
</StackPanel>
<StackPanel Grid.Column="1">
<Label>Firstname:</Label>
<Label>Lastname:</Label>
<Label>Age:</Label>
<Label>City:</Label>
</StackPanel>
<StackPanel Grid.Column="2" Margin="5" DataContext="{Binding SelectedItem, ElementName=listbox_Persons}">
<TextBox Text="{Binding FirstName}"/>
<TextBox Text="{Binding LastName}"/>
<TextBox Text="{Binding Age}"/>
<TextBox Text="{Binding City}"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Add" x:Name="btnAdd" Click="btnAdd_Click"/>
<Button Content="Delete" x:Name="btnDelete" Click="btnDelete_Click" />
</StackPanel>
</Grid>
</Window>
public partial class DetailView : Window
{
ObservableCollection<PersonDetailView> personList = new ObservableCollection<PersonDetailView>
{
new PersonDetailView { FirstName = "John", LastName = "Doe", Age = 25, City = "New York" },
new PersonDetailView { FirstName = "Jane", LastName = "Smith", Age = 30, City = "London" },
new PersonDetailView { FirstName = "Michael", LastName = "Johnson", Age = 35, City = "Berlin" },
new PersonDetailView { FirstName = "Emily", LastName = "Davis", Age = 28, City = "Paris" },
new PersonDetailView { FirstName = "Daniel", LastName = "Wilson", Age = 32, City = "Tokyo" },
new PersonDetailView { FirstName = "Sophia", LastName = "Taylor", Age = 29, City = "Sydney" },
new PersonDetailView { FirstName = "David", LastName = "Brown", Age = 27, City = "Toronto" },
new PersonDetailView { FirstName = "Olivia", LastName = "Miller", Age = 31, City = "Madrid" },
new PersonDetailView { FirstName = "Matthew", LastName = "Anderson", Age = 33, City = "Rome" },
new PersonDetailView { FirstName = "Emma", LastName = "Martinez", Age = 26, City = "Seoul" }
};

public DetailView()
{
InitializeComponent();
listbox_Persons.ItemsSource = personList;
listbox_Persons.SelectedIndex = 0;
listbox_Persons.Focus();
}

private void btnAdd_Click(object sender, RoutedEventArgs e)
{
PersonDetailView personDetailView = new PersonDetailView();
personList.Add(personDetailView);
listbox_Persons.SelectedItem = personDetailView;
}

private void btnDelete_Click(object sender, RoutedEventArgs e)
{
PersonDetailView personDetailView = (PersonDetailView)listbox_Persons.SelectedItem;
if (personDetailView != null)
{
personList.Remove(personDetailView);
}
}
}

Kommentare