Übersicht über die Datenbindung in WPF
Die Datenbindung in Windows Presentation Foundation (WPF) bietet für Apps eine einfache und konsistente Möglichkeit, Daten darzustellen und mit ihnen zu interagieren. Elemente können in Form von .NET-Objekten und XML an Daten aus einer Vielzahl von Datenquellen gebunden werden. ContentControl wie Button und ItemsControl wie ListBox und ListView verfügen über integrierte Funktionen, die eine flexible Formatierung von einzelnen Datenelementen oder Auflistungen von Datenelementen ermöglichen. Sortier-, Filter- und Gruppenansichten können übergreifend für die Daten generiert werden.
Die Datenbindungsfunktionen in WPF bieten gegenüber herkömmlichen Modellen einige Vorteile. Dazu zählen die grundsätzliche Unterstützung der Datenbindung durch eine breite Palette von Eigenschaften, eine flexible Darstellung von Daten auf der Benutzeroberfläche sowie die klare Trennung zwischen Geschäftslogik und Benutzeroberfläche.
In diesem Artikel werden zunächst die grundlegenden Konzepte der WPF-Datenbindung erläutert und anschließend die Verwendung der Binding-Klasse und anderer Datenbindungsfunktionen beschrieben.
Was ist Datenbindung?
Durch Datenbindung wird eine Verbindung zwischen der Benutzeroberfläche der App und den dort angezeigten Daten hergestellt. Wenn die Bindungseinstellungen korrekt sind und bei einer Änderung des Werts ordnungsgemäße Benachrichtigungen ausgegeben werden, werden die Elemente, die an die Daten gebunden sind, automatisch aktualisiert. Datenbindung kann auch bedeuten, dass bei einer Änderung der äußeren Darstellung von Daten in einem Element die zugrunde liegenden Daten automatisch aktualisiert werden können, um die Änderung wiederzugeben. Wenn der Benutzer beispielsweise den Wert in einem TextBox
-Element bearbeitet, wird der zugrunde liegende Datenwert automatisch aktualisiert, um diese Änderung anzuzeigen.
Eine typische Verwendung der Datenbindung wäre, die auf einem Server oder lokal gespeicherten Konfigurationsdaten in Formulare oder andere Steuerelemente der Benutzeroberfläche einzufügen. In WPF wird dieses Konzept dahingehend erweitert, dass auch eine Vielzahl von Eigenschaften an die unterschiedlichsten Datenquellen gebunden werden können. In WPF können Abhängigkeitseigenschaften von Elementen an .NET-Objekte (einschließlich ADO.NET-Objekte oder Objekte, die Webdiensten und Webeigenschaften zugeordnet sind) und XML-Daten gebunden werden.
Ein Beispiel für Datenbindung sehen Sie auf der folgenden App-Benutzeroberfläche aus der Demo für Datenbindung, die eine Liste von Auktionselementen zeigt.
Die App veranschaulicht die folgenden Datenbindungsfunktionen:
Der Inhalt von „ListBox“ ist an eine Auflistung von AuctionItem-Objekten gebunden. Ein AuctionItem-Objekt verfügt über Eigenschaften wie Description, StartPrice, StartDate, Category, SpecialFeatures usw.
Die Daten (AuctionItem-Objekte), die in
ListBox
angezeigt werden, sind vorlagenbasiert, sodass für jedes Element Beschreibung und aktueller Preis zu sehen sind. Die Vorlage wird mithilfe von DataTemplate erstellt. Darüber hinaus hängt die Darstellung jedes Elements vom SpecialFeatures-Wert des angezeigten AuctionItem ab. Wenn der SpecialFeatures-Wert von AuctionItem auf Color festgelegt ist, hat das Element einen blauen Rahmen. Wenn der Wert Highlight ist, hat das Element einen orangefarbenen Rahmen und einen Stern. Im Abschnitt Datenvorlagen erhalten Sie weitere Informationen zu Datenvorlagen.Der Benutzer kann die Daten mithilfe der bereitgestellten Kontrollkästchen (
CheckBoxes
) gruppieren, filtern oder sortieren. Im oben gezeigten Bild sind die Kontrollkästchen () Group by category undCheckBoxes
aktiviert. Möglicherweise haben Sie bereits bemerkt, dass die Daten nach der Kategorie des Produkts gruppiert sind und der Name der Kategorie in alphabetischer Reihenfolge aufgeführt ist. In der Abbildung ist nicht so gut zu erkennen, dass auch die Elemente innerhalb der einzelnen Kategorien nach Startdatum sortiert sind. Zur Sortierung wird eine Auflistungsansicht verwendet. Im Abschnitt Binden an Auflistungen werden Auflistungsansichten erläutert.Wenn der Benutzer ein Element auswählt, werden in ContentControl die Einzelheiten zum ausgewählten Element angezeigt. Dieses Verhalten wird als Master/Detail-Szenario bezeichnet. Im Abschnitt Master/Detail-Szenario erhalten Sie Informationen zu diesem Bindungstyp.
Der Typ der StartDate-Eigenschaft ist DateTime, wodurch ein Datum mit der Zeit bis auf die Millisekunde genau zurückgegeben wird. In dieser App wurde ein benutzerdefinierter Konverter verwendet, damit eine kürzere Datumszeichenfolge angezeigt wird. Weitere Informationen zu Konvertern finden Sie im Abschnitt Datenkonvertierung.
Wenn der Benutzer die Schaltfläche Add Product auswählt, wird das folgende Formular aufgerufen.
Der Benutzer kann die Felder im Formular bearbeiten, die Produktauflistung in der Kurzvorschau oder der detaillierten Vorschau anzeigen und Submit
auswählen, um die neue Produktauflistung hinzuzufügen. Alle vorhandenen Gruppierungs-, Filter- und Sortiereinstellungen werden auf den neuen Eintrag angewendet. In diesem speziellen Fall wird das im obigen Bild eingegebene Element in der Kategorie Computer an zweiter Stelle angezeigt.
Was in diesem Bild nicht angezeigt wird, ist die im -Element TextBox bereitgestellte Validierungslogik. Wenn der Benutzer ein ungültiges Datum eingibt (mit ungültiger Formatierung oder ein Datum in der Vergangenheit), wird er durch einen ToolTip und ein rotes Ausrufezeichen neben dem TextBox benachrichtigt. Im Abschnitt Datenvalidierung wird näher auf das Erstellen von Validierungslogik eingegangen.
Bevor die oben genannten unterschiedlichen Datenbindungsfunktionen näher erläutert werden, wird zunächst auf die grundlegenden Konzepte eingegangen, die für ein Verständnis der WPF-Datenbindung unerlässlich sind.
Grundlegende Konzepte der Datenbindung
Unabhängig davon, welches Element Sie binden und welcher Art die Datenquelle ist, erfolgt die Bindung stets gemäß dem in der folgenden Abbildung gezeigten Modell.
Wie in der Abbildung gezeigt, ist die Datenbindung die Brücke zwischen dem Bindungsziel und der Bindungsquelle. Die Abbildung veranschaulicht die folgenden grundlegenden Konzepte der WPF-Datenbindung:
Eine Bindung besteht in der Regel aus vier Komponenten:
- Bindungszielobjekt
- Zieleigenschaft
- Bindungsquelle
- Pfad zum Wert in der zu verwendenden Bindungsquelle
Angenommen, Sie möchten den Inhalt von
TextBox
an dieEmployee.Name
-Eigenschaft binden. Dann ist Ihr ZielobjektTextBox
, die Zieleigenschaft die Text-Eigenschaft, der zu verwendende Wert Name und das Quellobjekt das Employee-Objekt.Die Zieleigenschaft muss eine Abhängigkeitseigenschaft sein. Die meisten UIElement-Eigenschaften sind Abhängigkeitseigenschaften, und die meisten Abhängigkeitseigenschaften, mit Ausnahme der schreibgeschützten, unterstützen standardmäßig die Datenbindung. (Nur Typen, die von DependencyObject abgeleitet werden, können Abhängigkeitseigenschaften definieren, und alle UIElement-Typen werden von
DependencyObject
abgeleitet.)Obwohl nicht in der Abbildung dargestellt, sollte beachtet werden, dass das Bindungsquellenobjekt nicht darauf beschränkt ist, als benutzerdefiniertes .NET-Objekt zu fungieren. Die WPF-Datenbindung unterstützt Daten in Form von .NET-Objekten und XML. So könnte es sich bei Ihrer Bindungsquelle beispielsweise um ein UIElement handeln, ein beliebiges Listenobjekt, ein ADO.NET- oder Webdienste-Objekt oder um einen XmlNode, der Ihre XML-Daten enthält. Weitere Informationen finden Sie unter Übersicht über Bindungsquellen.
Sie sollten daran denken, dass Sie beim Einrichten einer Bindung ein Bindungsziel an eine Bindungsquelle binden. Wenn Sie z. B. zugrunde liegende XML-Daten mithilfe der Datenbindung in ListBox anzeigen, binden Sie ListBox
an die XML-Daten.
Verwenden Sie zum Einrichten einer Bindung das Binding-Objekt. Im weiteren Verlauf dieses Artikels werden zahlreiche Konzepte zum Binding
-Objekt, einige Eigenschaften und seine Verwendung erläutert.
Richtung des Datenflusses
Wie durch den Pfeil in der vorherigen Abbildung gezeigt, kann der Datenfluss einer Bindung vom Bindungsziel zur Bindungsquelle verlaufen (z. B. ändert sich der Quellwert, wenn ein Benutzer den Wert von TextBox
bearbeitet) und/oder von der Bindungsquelle zum Bindungsziel (z. B. wenn der TextBox
-Inhalt durch Änderungen in der Bindungsquelle aktualisiert wird), wenn die Bindungsquelle die entsprechenden Benachrichtigungen bereitstellt.
Sie können die App so einrichten, dass Benutzer die Daten ändern und zurück an das Quellobjekt übertragen können. Sie können auch das Aktualisieren von Quelldaten durch Benutzer unterbinden. Sie können den Datenfluss steuern, indem Sie Binding.Modefestlegen.
In dieser Abbildung werden die unterschiedlichen Datenflusstypen veranschaulicht:
Die OneWay-Bindung bewirkt, dass bei Änderungen an der Quelleigenschaft die Zieleigenschaft automatisch aktualisiert wird, ohne dass Änderungen an der Zieleigenschaft an die Quelleigenschaft zurückübertragen werden. Dieser Bindungstyp empfiehlt sich, wenn das gebundene Steuerelement implizit als schreibgeschützt festgelegt wurde. Sie können beispielsweise eine Bindung an eine Quelle wie einen Börsenticker erstellen, oder möglicherweise ist für die Zieleigenschaft keine Steuerungsschnittstelle verfügbar, um Änderungen vorzunehmen, z. B. an der datengebundenen Hintergrundfarbe einer Tabelle. Wenn die Änderungen der Zieleigenschaft nicht überwacht werden müssen, vermeiden Sie mit dem OneWay-Bindungsmodus den Mehraufwand des TwoWay-Bindungsmodus.
Die TwoWay-Bindung bewirkt, dass bei Änderungen an der Quell- bzw. der Zieleigenschaft die jeweils andere automatisch aktualisiert wird. Dieser Typ von Bindung ist für bearbeitbare Formulare und sonstige vollständig interaktive Benutzeroberflächenszenarien geeignet. Die meisten Eigenschaften sind standardmäßig auf die OneWay-Bindung festgelegt, aber einige Abhängigkeitseigenschaften (meistens Eigenschaften von Steuerelementen, die benutzerseitig bearbeitet werden können, z. B. TextBox.Text und CheckBox.IsChecked) sind standardmäßig auf die TwoWay-Bindung festgelegt. Eine programmgesteuerte Methode zum Bestimmen, ob eine Abhängigkeitseigenschaft standardmäßig uni- oder bidirektional bindet, besteht darin, die Eigenschaftenmetadaten mit DependencyProperty.GetMetadata abzurufen und dann den booleschen Wert der FrameworkPropertyMetadata.BindsTwoWayByDefault-Eigenschaft zu überprüfen.
Das Gegenteil von OneWayToSource ist die OneWay-Bindung, bei der die Quelleigenschaft aktualisiert wird, sobald die Zieleigenschaft geändert wird. Ein Beispielszenario dafür ist, wenn nur der Quellwert von der Benutzeroberfläche neu bewertet werden muss.
In der Abbildung nicht gezeigt wird die OneTime-Bindung, die bewirkt, dass die Quelleigenschaft die Zieleigenschaft initialisiert, nachfolgende Änderungen jedoch nicht weitergegeben werden. Wenn sich der Datenkontext oder das Objekt im Datenkontext ändert, wird die Änderung in der Zieleigenschaft nicht angezeigt. Dieser Bindungstyp empfiehlt sich, wenn entweder eine Momentaufnahme des aktuellen Zustands verwendet werden kann oder die Daten tatsächlich statisch sind. Dieser Bindungstyp ist auch hilfreich, wenn die Zieleigenschaft mit einem bestimmten Wert der Quelleigenschaft initialisiert werden soll und der Datenkontext vorab nicht bekannt ist. Dieser Modus ist im Wesentlichen eine einfachere Form der OneWay-Bindung, die eine bessere Leistung in Situationen bietet, in denen sich der Quellwert nicht ändert.
Zum Erkennen von Quelländerungen (das gilt für die OneWay-Bindung und die TwoWay-Bindung) muss die Quelle einen geeigneten Mechanismus für Benachrichtigungen bei Eigenschaftenänderungen implementieren, z. B. INotifyPropertyChanged. Weitere Informationen finden Sie unter How to: Implementieren von Benachrichtigungen bei Eigenschaftenänderungen. Dort finden Sie auch ein Beispiel für eine INotifyPropertyChanged-Implementierung.
Die Binding.Mode-Eigenschaft bietet weitere Informationen zu Bindungsmodi und ein Beispiel zum Angeben der Bindungsrichtung.
Wodurch werden Quellaktualisierungen ausgelöst?
Bindungen vom Typ TwoWay oder OneWayToSource überwachen die Zieleigenschaft auf Änderungen und übertragen sie zurück an die Quelle. Dies wird als Aktualisieren der Quelle bezeichnet. Sie können beispielsweise den Text eines TextBox-Elements bearbeiten, um den zugrunde liegenden Quellwert zu ändern.
Wird der Quellwert aktualisiert, während Sie den Text bearbeiten oder nachdem Sie die Bearbeitung abgeschlossen haben und das Steuerelement den Fokus verliert? Die Binding.UpdateSourceTrigger-Eigenschaft bestimmt, wodurch die Aktualisierung der Quelle ausgelöst wird. Die Punkte der nach rechts weisenden Pfeile in der folgenden Abbildung veranschaulichen die Rolle der Binding.UpdateSourceTrigger-Eigenschaft.
Ist der UpdateSourceTrigger
-Wert auf UpdateSourceTrigger.PropertyChanged festgelegt, wird der Wert, auf den mit dem nach rechts weisenden Pfeil der TwoWay-Bindung oder der OneWayToSource-Bindungen gezeigt wird, aktualisiert, sobald sich die Zieleigenschaft ändert. Ist der UpdateSourceTrigger
-Wert hingegen auf LostFocus festgelegt, wird dieser Wert nur dann durch den neuen Wert aktualisiert, wenn die Zieleigenschaft den Fokus verliert.
Wie bei der Mode-Eigenschaft haben verschiedene Abhängigkeitseigenschaften unterschiedliche UpdateSourceTrigger-Standardwerte. Der Standardwert für die meisten Abhängigkeitseigenschaften ist PropertyChanged, während die TextBox.Text
-Eigenschaft den Standardwert LostFocus aufweist. PropertyChanged
bedeutet, dass Quellaktualisierungen in der Regel bei jeder Änderung der Zieleigenschaft erfolgen. Sofortige Änderungen sind bei Kontrollkästchen und anderen einfachen Steuerelementen unproblematisch. Bei Textfeldern kann eine Aktualisierung nach jeder Tastatureingabe jedoch die Leistung mindern und führt außerdem dazu, dass der Benutzer nicht wie gewohnt durch Drücken der Rücktaste Tippfehler beheben kann, bevor der neue Wert übergeben wird.
Auf der UpdateSourceTrigger-Eigenschaftenseite erhalten Sie weitere Informationen zur Suche nach dem Standardwert einer Abhängigkeitseigenschaft.
In der folgenden Tabelle sehen Sie ein Beispielszenario für jeden UpdateSourceTrigger-Wert, wobei TextBox als Beispiel verwendet wird.
UpdateSourceTrigger-Wert | Wenn der Quellwert aktualisiert wird | Beispielszenario für TextBox |
---|---|---|
LostFocus (Standard für TextBox.Text) |
Wenn das TextBox-Steuerelement den Fokus verliert. | Ein TextBox-Element, dem eine Validierungslogik zugeordnet ist (siehe Abschnitt Datenvalidierung weiter unten). |
PropertyChanged |
Während der Eingabe in TextBox. | TextBox-Steuerelemente in einem Chatroomfenster. |
Explicit |
Wenn die App UpdateSource aufruft. | TextBox-Steuerelemente in einem bearbeitbaren Formular (aktualisiert die Quellwerte nur dann, wenn der Benutzer auf die Schaltfläche zum Senden klickt). |
Ein Beispiel finden Sie unter Gewusst wie: Steuern, wann der TextBox-Text die Quelle aktualisiert.
Erstellen einer Bindung
Hier noch einmal eine Wiederholung der in den vorherigen Abschnitten vorgestellten Konzepte: Sie richten eine Bindung mithilfe des Binding-Objekts ein. Jede Bindung besteht in der Regel aus vier Komponenten. Dies sind Bindungsziel, Zieleigenschaft, Bindungsquelle sowie ein Pfad zu dem zu verwendenden Quellwert. In diesem Abschnitt wird das Einrichten einer Bindung erläutert.
Im folgenden Beispiel ist das Bindungsquellobjekt eine Klasse namens MyData, die im Namespace SDKSample definiert ist. Zu Demonstrationszwecken weist MyData eine Zeichenfolgeneigenschaft namens ColorName auf, deren Wert auf „Red“ festgelegt ist. In diesem Beispiel wird also eine Schaltfläche mit einem roten Hintergrund erstellt.
<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample">
<DockPanel.Resources>
<c:MyData x:Key="myDataSource"/>
</DockPanel.Resources>
<DockPanel.DataContext>
<Binding Source="{StaticResource myDataSource}"/>
</DockPanel.DataContext>
<Button Background="{Binding Path=ColorName}"
Width="150" Height="30">
I am bound to be RED!
</Button>
</DockPanel>
Weitere Informationen zur Syntax der Bindungsdeklaration und Beispiele zum Einrichten einer Bindung im Code finden Sie unter Übersicht über Bindungsdeklarationen.
Wenn dieses Beispiel auf das einfache Diagramm angewendet wird, sieht die resultierend Abbildung wie die folgende aus. Diese Abbildung zeigt eine OneWay-Bindung, da die Background-Eigenschaft standardmäßig OneWay-Bindungen unterstützt.
Sie fragen sich vielleicht, warum diese Bindung funktioniert, obwohl die ColorName-Eigenschaft ein Zeichenfolgentyp ist, während die Background-Eigenschaft vom Typ Brush ist. Bei dieser Bindung wird die Standardtypkonvertierung verwendet, die im Abschnitt Datenkonvertierung erläutert wird.
Angeben der Bindungsquelle
Beachten Sie, dass im vorherigen Beispiel die Bindungsquelle durch Festlegen der DockPanel.DataContext-Eigenschaft angegeben wurde. Button erbt dann den DataContext-Wert von DockPanel, wobei es sich um das übergeordnete Element handelt. Das Bindungsquellobjekt stellt, wie bereits erwähnt, eine der vier erforderlichen Komponenten einer Bindung dar. Wäre das Bindungsquellobjekt nicht angegeben, hätte die Bindung keine Auswirkungen.
Es gibt mehrere Möglichkeiten, das Bindungsquellobjekt anzugeben. Die Verwendung der DataContext-Eigenschaft für ein übergeordnetes Element ist hilfreich, wenn Sie mehrere Eigenschaften an dieselbe Quelle binden. Es kann aber auch zweckmäßiger sein, die Bindungsquelle in einzelnen Bindungsdeklarationen anzugeben. Im vorherigen Beispiel können Sie auch statt der DataContext-Eigenschaft die Bindungsquelle angeben, indem Sie die Binding.Source-Eigenschaft direkt in der Bindungsdeklaration der Schaltfläche festlegen. Dies wird im folgenden Beispiel gezeigt.
<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample">
<DockPanel.Resources>
<c:MyData x:Key="myDataSource"/>
</DockPanel.Resources>
<Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
Width="150" Height="30">
I am bound to be RED!
</Button>
</DockPanel>
Statt die DataContext-Eigenschaft direkt für ein Element festzulegen, den DataContext-Wert von einem übergeordneten Element zu erben (z. B. die Schaltfläche im ersten Beispiel) und die Bindungsquelle explizit durch Festlegen der Binding.Source-Eigenschaft in der Bindung anzugeben (z. B. die Schaltfläche im letzten Beispiel), können Sie zum Angeben der Bindungsquelle auch die Binding.ElementName-Eigenschaft oder die Binding.RelativeSource-Eigenschaft verwenden. Die ElementName-Eigenschaft ist hilfreich zum Binden an andere Elemente in der App, beispielsweise wenn Sie die Breite einer Schaltfläche mit einem Schieberegler anpassen. Die RelativeSource-Eigenschaft bietet sich an, wenn die Bindung in ControlTemplate oder Style angegeben ist. Weitere Informationen finden Sie unter Vorgehensweise: Angeben der Bindungsquelle.
Angeben des Pfads zum Wert
Wenn die Bindungsquelle ein Objekt ist, verwenden Sie die Binding.Path-Eigenschaft, um den für die Bindung zu verwendenden Wert anzugeben. Wenn Sie eine Bindung an XML-Daten vornehmen, geben Sie den Wert mithilfe der Binding.XPath-Eigenschaft an. In einigen Fällen können Sie die Path-Eigenschaft auch dann verwenden, wenn es sich bei den Daten um XML handelt. Angenommen, Sie möchten auf die Name-Eigenschaft eines zurückgegebenen XmlNode zugreifen (als Ergebnis einer XPath-Abfrage), dann sollten Sie die Path-Eigenschaft zusätzlich zur XPath-Eigenschaft verwenden.
Weitere Informationen finden Sie in den Ausführungen zur Path-Eigenschaft und zur XPath-Eigenschaft.
Obwohl darauf hingewiesen wurde, dass der zu verwendende Path zum Wert eine der vier erforderlichen Komponenten einer Bindung darstellt, ist der in den Szenarien zum Binden an ein vollständiges Objekt zu verwendende Wert mit dem Bindungsquellobjekt identisch. In einem solchen Fall muss kein Path angegeben werden. Betrachten Sie das folgende Beispiel.
<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="true"/>
Im obigen Beispiel wird die leere Bindungssyntax verwendet: {Binding}. In diesem Fall erbt ListBox den DataContext von einem übergeordneten DockPanel-Element (was in diesem Beispiel nicht gezeigt wird). Wenn der Pfad nicht angegeben wurde, erfolgt die Bindung standardmäßig an das gesamte Objekt. Das heißt, dass der Pfad in diesem Beispiel ausgelassen wird, da die ItemsSource-Eigenschaft an das gesamte Objekt gebunden wird. (Im Abschnitt Binden an Auflistungen wird ausführlich darauf eingegangen.)
Dieses Szenario ist nicht nur zum Binden an eine Auflistung hilfreich, sondern auch zum Binden an ein vollständiges Objekt statt nur an eine einzelne Eigenschaft eines Objekts. Wenn es sich beim Quellobjekt beispielsweise um den Typ String handelt, möchten Sie vielleicht lediglich eine Bindung an die Zeichenfolge selbst vornehmen. Ein anderes allgemeines Szenario besteht darin, ein Element an ein Objekt mit mehreren Eigenschaften zu binden.
Möglicherweise müssen Sie benutzerdefinierte Logik anwenden, damit die Daten für die gebundene Zieleigenschaft sinnvoll sind. Bei der benutzerdefinierten Logik kann es sich z. B. um einen benutzerdefinierten Konverter handeln, falls keine Standardtypkonvertierung vorhanden ist. Weitere Informationen über Konverter finden Sie unter Datenkonvertierung.
Die „Binding“-Klasse und „BindingExpression“
Bevor auf weitere Features und Verwendungsmöglichkeiten der Datenbindung eingegangen wird, ist es sinnvoll, sich mit der BindingExpression-Klasse zu befassen. Wie die vorherigen Abschnitte gezeigt haben, handelt es sich bei der Binding-Klasse um die Klasse höherer Ebene für die Deklaration einer Bindung. Diese Klasse bietet zahlreiche Eigenschaften, mit denen Sie die Merkmale einer Bindung angeben können. Die verwandte BindingExpression-Klasse ist das zugrunde liegende Objekt, das die Verbindung zwischen Quelle und Ziel aufrechterhält. Ein Bindung enthält sämtliche Informationen, die von mehreren Bindungsausdrücken gemeinsam genutzt werden können. BindingExpression ist ein Instanzenausdruck, der nicht gemeinsam genutzt werden kann und sämtliche Instanzeninformationen von Binding enthält.
Sehen Sie sich das folgende Beispiel an, bei dem myDataObject
eine Instanz der MyData
-Klasse ist, myBinding
das Quellobjekt von Binding ist und MyData
eine definierte Klasse ist, die eine Zeichenfolgeneigenschaft namens ColorName
enthält. In diesem Beispiel wird der Textinhalt von myText
, einer Instanz von TextBlock, an ColorName
gebunden.
// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
Source = myDataObject
};
// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject
' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)
Mit demselben myBinding-Objekt können Sie auch andere Bindungen erstellen. Sie können beispielsweise mit dem myBinding-Objekt eine Bindung des Textinhalts eines Kontrollkästchens an ColorName herstellen. In diesem Szenario werden zwei Instanzen von BindingExpression verwendet, die das myBinding-Objekt gemeinsam nutzen.
Ein BindingExpression-Objekt wird durch Aufrufen von GetBindingExpression für ein datengebundenes Objekt zurückgegeben. In den folgenden Artikeln werden einige Verwendungsmöglichkeiten der BindingExpression-Klasse veranschaulicht:
Datenkonvertierung
Im Abschnitt Erstellen einer Bindung ist die Schaltfläche rot, da die Background-Eigenschaft an eine Zeichenfolgeneigenschaft mit dem Wert „Red“ gebunden ist. Dieser Zeichenfolgenwert funktioniert, weil ein Typkonverter für den Brush-Typ vorhanden ist, mit dem der Zeichenfolgenwert in Brush konvertiert wird.
Wenn diese Informationen der Abbildung im Abschnitt Erstellen einer Bindung hinzugefügt werden, sieht das Ergebnis folgendermaßen aus.
Was geschieht jedoch, wenn keine Eigenschaft mit einem Zeichenfolgentyp vorhanden ist, sondern das Bindungsquellobjekt eine -Eigenschaft mit dem Typ Color aufweist? Damit in diesem Fall die Bindung funktioniert, müssten Sie den Color-Eigenschaftswert zuerst in einen Wert ändern, der von der Background-Eigenschaft akzeptiert wird. Dazu müssten Sie einen benutzerdefinierten Konverter erstellen, indem Sie die IValueConverter-Schnittstelle implementieren, wie es im folgenden Beispiel gezeigt ist.
[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Color color = (Color)value;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim color As Color = CType(value, Color)
Return New SolidColorBrush(color)
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Nothing
End Function
End Class
Weitere Informationen finden Sie unter IValueConverter.
Jetzt wird anstelle der Standardkonvertierung der benutzerdefinierte Konverter verwendet, und das Diagramm sieht folgendermaßen aus.
Wie bereits ausgeführt, können aufgrund von Typkonvertern, die im Typ vorhanden sind, an den gebunden wird, Standardkonvertierungen verfügbar sein. Dieses Verhalten hängt von den im Ziel vorhandenen Typkonvertern ab. Erstellen Sie im Zweifelsfall einen eigenen Konverter.
Es folgen einige typische Szenarien, in denen die Implementierung eines Datenkonverters sinnvoll ist:
Die Daten sollen je nach Kultur unterschiedlich angezeigt werden. Sie können z. B. einen Währungskonverter oder einen Datum-/Uhrzeitkonverter implementieren, der auf den Konventionen in einer bestimmten Kultur basiert.
Die verwendeten Daten müssen nicht unbedingt dazu dienen, den Textwert einer Eigenschaft zu ändern. Sie können auch den Zweck haben, andere Werte zu ändern, beispielsweise die Quelle für ein Bild oder die Farbe bzw. das Format des Anzeigetexts. Konverter können in dieser Instanz verwendet werden, indem die Bindung einer Eigenschaft konvertiert wird, die ungeeignet zu sein scheint, z. B. wenn ein Textfeld an die Background-Eigenschaft einer Tabellenzelle gebunden wird.
Mehrere Steuerelemente oder mehrere Steuerelementeigenschaften sind an dieselben Daten gebunden. In diesem Fall wird durch die primäre Bindung möglicherweise nur der Text angezeigt, wohingegen andere Bindungen bestimmte Anzeigeprobleme behandeln, aber dieselben Bindungen als Quellinformationen verwenden.
Eine Zieleigenschaft verfügt über eine Auflistung von Bindungen. Dies wird als MultiBinding bezeichnet. Bei MultiBinding verwenden Sie einen benutzerdefinierten IMultiValueConverter, um einen endgültigen Wert aus den Werten der Bindungen zu generieren. So kann die Farbe beispielsweise aus roten, blauen und grünen Werten berechnet werden, die aus denselben oder anderen Bindungsquellobjekten stammen können. Beispiele und weitere Informationen finden Sie unter MultiBinding.
Binden an Auflistungen
Ein Bindungsquellobjekt kann entweder als einzelnes Objekt behandelt werden, dessen Eigenschaften Daten enthalten, oder als eine Datenauflistung von polymorphen Objekten, die häufig zusammen gruppiert werden (beispielsweise als Ergebnis einer Datenbankabfrage). Bisher wurde nur das Binden an einzelne Objekte behandelt. Das Binden an eine Datenauflistung ist jedoch ebenfalls ein gängiges Szenario. So besteht ein häufiges Szenario darin, ein ItemsControl wie ListBox, ListView oder TreeView zu verwenden, um eine Datenauflistung anzuzeigen. Ein Beispiel finden Sie in der App, die im Abschnitt Was ist Datenbindung? verwendet wird.
Das bisherige einfache Diagramm ist praktischerweise immer noch gültig. Wenn Sie ItemsControl an eine Auflistung binden, sieht das Diagramm folgendermaßen aus.
Wie in diesem Diagramm gezeigt, muss zum Binden von ItemsControl an ein Auflistungsobjekt die ItemsControl.ItemsSource-Eigenschaft verwendet werden. Sie können sich die ItemsSource
-Eigenschaft als Inhalt von ItemsControl vorstellen. Die Bindung ist OneWay, da die ItemsSource
-Eigenschaft standardmäßig die OneWay
-Bindung unterstützt.
Implementieren von Auflistungen
Sie können jede Auflistung auflisten, die die IEnumerable-Schnittstelle implementiert. Um dynamische Bindungen einzurichten, bei denen die Benutzeroberfläche automatisch nach Einfügungen oder Löschungen in der Auflistung aktualisiert wird, muss die Auflistung die INotifyCollectionChanged-Schnittstelle implementieren. Diese Schnittstelle macht ein Ereignis verfügbar, das bei jeder Änderung der zugrunde liegenden Auflistung ausgelöst werden sollte.
WPF stellt die ObservableCollection<T>-Klasse bereit, bei der es sich um die integrierte Implementierung einer Datenauflistung handelt, die die INotifyCollectionChanged-Schnittstelle verfügbar macht. Um eine vollständige Unterstützung für die Übertragung von Datenwerten von Quellobjekten zu Zielen zu gewährleisten, muss jedes Objekt in der Auflistung, die bindbare Eigenschaften unterstützt, auch die INotifyPropertyChanged-Schnittstelle implementieren. Weitere Informationen finden Sie unter Übersicht über Bindungsquellen.
Bevor Sie eine eigene Auflistung implementieren, erwägen Sie ObservableCollection<T> oder einer vorhandenen Sammlung Klassen, z. B. List<T>, Collection<T>, und BindingList<T>, a. Falls Sie für ein erweitertes Szenario Ihre eigene Auflistung implementieren möchten, können Sie IList und somit eine nicht generische Auflistung von Objekten verwenden, auf die einzeln über den Index zugegriffen werden kann. So lässt sich eine optimale Leistung erzielen.
Auflistungsansichten
Nachdem ItemsControl an eine Datenauflistung gebunden wurde, können Sie die Daten sortieren, filtern oder gruppieren. Hierzu verwenden Sie Auflistungsansichten, bei denen es sich um Klassen handelt, die die ICollectionView-Schnittstelle implementieren.
Was sind Auflistungsansichten?
Eine Auflistungsansicht fungiert als Ebene über der Bindungsquellauflistung, in der Sie mit Sortier-, Filter- und Gruppierungsabfragen navigieren und die jeweilige Quellauflistung anzeigen können, ohne die zugrunde liegende Quellauflistung selbst ändern zu müssen. Eine Auflistungsansicht stellt außerdem einen Zeiger auf das aktuelle Element in der Auflistung zur Verfügung. Wenn die Quellauflistung die INotifyCollectionChanged-Schnittstelle implementiert, werden die vom CollectionChanged-Ereignis ausgelösten Änderungen an die Ansichten weitergegeben.
Da in Ansichten die zugrunde liegenden Quellauflistungen nicht geändert werden, können einer Quellauflistung mehrere Ansichten zugeordnet sein. Angenommen, Sie verfügen über eine Auflistung von Task-Objekten. Mithilfe von Ansichten können Sie dieselben Daten auf verschiedene Weise anzeigen. Beispielsweise können Sie links auf der Seite Aufgaben nach Priorität und rechts nach Bereich sortieren.
Erstellen einer Ansicht
Eine Möglichkeit, eine Ansicht zu erstellen und zu verwenden, besteht darin, das Ansichtsobjekt direkt zu instanziieren und dann als Bindungsquelle zu verwenden. Betrachten Sie z. B. die App Demo für Datenbindung, die im Abschnitt Was ist Datenbindung? gezeigt wird. Die App wurde so implementiert, dass ListBox an eine Ansicht über die Datenauflistung gebunden wird, anstatt direkt an die Datenauflistung. Das folgende Beispiel stammt aus der Demo-App für Datenbindung. Die CollectionViewSource-Klasse ist der XAML-Proxy einer Klasse, die von CollectionViewerbt. In diesem speziellen Beispiel ist die Source der Ansicht an die AuctionItems-Auflistung (des Typs ObservableCollection<T>) des aktuellen Anwendungsobjekts gebunden.
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"
x:Key="listingDataView" />
</Window.Resources>
Die Ressource listingDataView dient dann als Bindungsquelle für Elemente in der App, z. B. ListBox.
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
Wenn Sie eine weitere Ansicht für dieselbe Auflistung erstellen möchten, können Sie eine weitere CollectionViewSource-Instanz erstellen und ihr einen anderen x:Key
-Namen geben.
Die folgende Tabelle zeigt, welche Ansichtsdatentypen als Standardauflistungsansicht bzw. mit CollectionViewSource basierend auf dem Quellauflistungstyp erstellt werden.
Quellauflistungstyp | Auflistungsansichtstyp | Hinweise |
---|---|---|
IEnumerable | Ein interner auf CollectionView basierender Typ | Kann Elemente nicht gruppieren. |
IList | ListCollectionView | Am schnellsten. |
IBindingList | BindingListCollectionView |
Verwenden einer Standardansicht
Die Angabe einer Auflistungsansicht als Bindungsquelle ist eine Möglichkeit, eine Auflistungsansicht zu erstellen und zu verwenden. WPF erstellt außerdem eine Standardauflistungsansicht für jede als Bindungsquelle verwendete Auflistung. Bei der direkten Bindung an eine Auflistung bindet WPF an die Standardansicht der Auflistung. Diese Standardansicht wird von allen Bindungen an diese Auflistung gemeinsam verwendet, sodass eine Änderung an der Standardansicht durch ein gebundenes Steuerelement oder Code, z. B. Sortierung oder eine Änderung des aktuellen Elementzeigers (dies wird zu einem späteren Zeitpunkt erläutert), sich auf alle anderen Bindungen an dieselbe Auflistung auswirkt.
Zum Abrufen der Standardansicht verwenden Sie die GetDefaultView-Methode. Ein Beispiel finden Sie unter Abrufen der Standardansicht einer Datenauflistung.
Auflistungsansichten mit ADO.NET-Datentabellen
Zur Leistungsoptimierung wird bei Auflistungsansichten für ADO.NET die Sortierung und Filterung von DataTable- oder DataView-Objekten an DataView delegiert. So wird die Sortierung und Filterung von allen Auflistungsansichten der Datenquelle gemeinsam verwendet. Um für die einzelnen Auflistungsansichten eine eigene Sortierung und Filterung zu ermöglichen, müssen Sie jede Auflistungsansicht mit einem eigenen DataView-Objekt initialisieren.
Sortieren
Wie bereits erwähnt, können mit Ansichten Sortierreihenfolgen auf Auflistungen angewendet werden. Da sie in der zugrunde liegenden Auflistung vorhanden sind, haben die Daten möglicherweise eine relevante inhärente Reihenfolge oder nicht. Die Ansicht der Auflistung ermöglicht Ihnen das Festlegen einer Reihenfolge oder das Ändern der Standardreihenfolge, je nachdem, welche Vergleichskriterien Sie angeben. Da es sich um eine clientbasierte Ansicht der Daten handelt, besteht ein übliches Szenario darin, dass der Benutzer Spalten mit Tabellendaten nach dem Wert sortiert, dem die Spalte entspricht. Mithilfe von Ansichten kann diese benutzerdefinierte Sortierung angewendet werden, ohne die zugrunde liegende Auflistung ändern oder den Auflistungsinhalt erneut abfragen zu müssen. Ein Beispiel finden Sie unter Sortieren einer GridView-Spalte beim Klicken auf einen Header.
Das folgende Beispiel zeigt die Sortierlogik des Kontrollkästchens (CheckBox) „Sort by category and date“, das auf der Benutzeroberfläche der App im Abschnitt Was ist Datenbindung? verwendet wird.
private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
// Sort the items first by Category and then by StartDate
listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
' Sort the items first by Category And then by StartDate
listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub
Filtern
Ansichten können auch einen Filter auf eine Auflistung anwenden, sodass in der Ansicht nur eine bestimmte Teilmenge der gesamten Auflistung angezeigt wird. Sie können die Daten nach einer Bedingung filtern. In der App im Abschnitt Was ist Datenbindung? enthält das Kontrollkästchen (CheckBox) „Show only bargains“ z. B. eine Logik, mit der Elemente herausgefiltert werden, die mehr als 25 Dollar kosten. Der folgende Code wird ausgeführt, um ShowOnlyBargainsFilter als den Filter-Ereignishandler festzulegen, wenn CheckBox aktiviert wird.
private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (((CheckBox)sender).IsChecked == true)
listingDataView.Filter += ListingDataView_Filter;
else
listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
Dim checkBox = DirectCast(sender, CheckBox)
If checkBox.IsChecked = True Then
AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
Else
RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
End If
End Sub
Der ShowOnlyBargainsFilter-Ereignishandler hat die folgende Implementierung.
private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
// Start with everything excluded
e.Accepted = false;
// Only inlcude items with a price less than 25
if (e.Item is AuctionItem product && product.CurrentPrice < 25)
e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)
' Start with everything excluded
e.Accepted = False
Dim product As AuctionItem = TryCast(e.Item, AuctionItem)
If product IsNot Nothing Then
' Only include products with prices lower than 25
If product.CurrentPrice < 25 Then e.Accepted = True
End If
End Sub
Wenn Sie direkt eine der CollectionView-Klassen anstelle von CollectionViewSource verwenden, verwenden Sie die Filter-Eigenschaft zum Angeben eines Rückrufs. Ein Beispiel finden Sie unter Filtern von Daten in einer Ansicht.
Gruppierung
Mit Ausnahme der internen Klasse zur Ansicht einer IEnumerable-Auflistung unterstützen alle Auflistungsansichten eine Gruppierung, wodurch der Benutzer die Auflistung in der Auflistungsansicht in logische Gruppen unterteilen kann. Die Gruppen können explizit sein, wobei Benutzer eine Liste von Gruppen angeben. Sie können auch implizit sein, wobei die Gruppen dynamisch in Abhängigkeit von den Daten generiert werden.
Das folgende Beispiel zeigt die Logik des Kontrollkästchens (CheckBox) „Group by category“.
// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)
Ein weiteres Beispiel zu Gruppierungen finden Sie unter Gruppieren von Elementen in einem ListView, in dem ein GridView implementiert ist.
Zeiger auf aktuelle Elemente
Ansichten unterstützen ebenfalls das Konzept eines aktuellen Elements. Sie können durch die Objekte in einer Auflistungsansicht navigieren. Beim Navigieren verschieben Sie einen Elementzeiger, mit dem Sie das Objekt abrufen können, das sich an einer bestimmten Position in der Auflistung befindet. Ein Beispiel finden Sie unter Navigieren durch die Objekte in einer Datenauflistungsansicht.
Da WPF Bindungen an Auflistungen immer über Ansichten herstellt (entweder über eine von Ihnen erstellte Ansicht oder über die Standardansicht der jeweiligen Auflistung), verfügen alle Bindungen an Auflistungen über einen Zeiger auf das aktuelle Element. Bei der Bindung an eine Ansicht gibt der Schrägstrich ("/") im Path
-Wert das aktuelle Element der Ansicht an. Der Datenkontext im folgenden Beispiel ist eine Auflistungsansicht. Die erste Zeile wird an die Auflistung gebunden. Die zweite Zeile wird an das aktuelle Element in der Auflistung gebunden. Die dritte Zeile wird an die Description
-Eigenschaft des aktuellen Elements in der Auflistung gebunden.
<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />
Der Schrägstrich und die Eigenschaftensyntax können gestapelt werden, um eine Hierarchie von Auflistungen zu durchlaufen. Im folgenden Beispiel erfolgt die Bindung an das aktuelle Element einer Auflistung mit dem Namen Offices
, wobei es sich um eine Eigenschaft des aktuellen Elements der Quellauflistung handelt.
<Button Content="{Binding /Offices/}" />
Der Zeiger auf das aktuelle Element kann durch eine Sortierung oder Filterung beeinflusst werden, die auf die Auflistung angewendet wird. Beim Sortieren verbleibt der Zeiger für das aktuelle Element auf dem zuletzt ausgewählten Element, die Auflistungsansicht wird jedoch nun um den Zeiger herum neu angeordnet. (Möglicherweise befand sich das ausgewählte Element zuvor am Anfang der Liste, jetzt aber in der Mitte.) Beim Filtern wird das ausgewählte Element beibehalten, wenn diese Auswahl nach dem Filtern in der Ansicht verbleibt. Andernfalls wird der Zeiger für das aktuelle Element auf das erste Element der gefilterten Auflistungsansicht festgelegt.
Szenario für Master-Detail-Bindung
Das Konzept eines aktuellen Elements ist nicht nur hilfreich, um durch Elemente in einer Auflistung zu navigieren, sondern auch für das Szenario einer Master-Detail-Bindung. Betrachten Sie wieder die Benutzeroberfläche der App, die im Abschnitt Was ist Datenbindung? verwendet wird. In dieser App wird durch die Auswahl in ListBox der in ContentControl angezeigte Inhalt bestimmt. Anders ausgedrückt: Wenn ein ListBox-Element ausgewählt wird, werden in ContentControl die Details zum ausgewählten Element angezeigt.
Sie können das Master-Detail-Szenario auch einfach dadurch implementieren, dass mindestens zwei Steuerelemente an dieselbe Ansicht gebunden sind. Im folgenden Beispiel aus der Demo für die Datenbindung ist das Markup von ListBox und ContentControl dargestellt, die auf der Benutzeroberfläche der App im Abschnitt Was ist Datenbindung? verwendet werden.
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
Content="{Binding Source={StaticResource listingDataView}}"
ContentTemplate="{StaticResource detailsProductListingTemplate}"
Margin="9,0,0,0"/>
Beachten Sie, dass beide Steuerelemente an dieselbe Quelle gebunden sind, und zwar die statische listingDataView-Ressource (siehe die Definition dieser Ressource im Abschnitt Erstellen einer Ansicht). Diese Bindung funktioniert deshalb, weil ein Objekt (in diesem Fall ContentControl) beim Binden an eine Collectionansicht automatisch an CurrentItem in der Ansicht gebunden wird. Die CollectionViewSource-Objekte synchronisieren automatisch Währung und Auswahl. Ist Ihr Listensteuerelement, anders als in diesem Beispiel, nicht an ein CollectionViewSource-Objekt gebunden, müssen Sie dessen IsSynchronizedWithCurrentItem-Eigenschaft auf true
festlegen, damit dies erfolgt.
Weitere Beispiele finden Sie unter Binden an eine Auflistung und Anzeigen von Informationen auf Grundlage der Auswahl und Verwenden des Master-/Detailmusters mit hierarchischen Daten.
Ihnen ist vielleicht aufgefallen, dass im vorherigen Beispiel eine Vorlage verwendet wird. Die Daten würden nicht wunschgemäß angezeigt, wenn keine Vorlagen verwendet würden (eine wird explizit von ContentControl verwendet und die andere implizit von ListBox). Im nächsten Abschnitt beschäftigen wir uns mit Datenvorlagen.
Datenvorlagen
Ohne Datenvorlagen würde die Benutzeroberfläche der App im Abschnitt Was ist Datenbindung? folgendermaßen aussehen.
Wie im Beispiel im vorherigen Abschnitt gezeigt, werden sowohl das ListBox-Steuerelement als auch ContentControl an das gesamte Auflistungsobjekt (oder genauer gesagt, die Ansicht des Auflistungsobjekts) von AuctionItem gebunden. Ohne spezielle Anzeigeanweisungen für die Datensammlung, wird ListBox als Zeichenfolgendarstellung der Objekte in der zugrunde liegenden Auflistung angezeigt, und ContentControl wird als Zeichenfolgendarstellung des Objekts angezeigt, an das es gebunden ist.
Um dieses Problem zu lösen, definiert die App DataTemplates (Datenvorlagen). Wie im Beispiel im vorherigen Abschnitt gezeigt, verwendet ContentControl explizit die detailsProductListingTemplate-Datenvorlage. Das ListBox-Steuerelement verwendet implizit die folgende Datenvorlage, wenn die AuctionItem-Objekte in der Auflistung angezeigt werden.
<DataTemplate DataType="{x:Type src:AuctionItem}">
<Border BorderThickness="1" BorderBrush="Gray"
Padding="7" Name="border" Margin="3" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
Fill="Yellow" Stroke="Black" StrokeThickness="1"
StrokeLineJoin="Round" Width="20" Height="20"
Stretch="Fill"
Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
Visibility="Hidden" Name="star"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
Name="descriptionTitle"
Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
<TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
Text="{Binding Path=Description}"
Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
Name="currentPriceTitle"
Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Name="CurrentPriceDTDataType"
Text="{Binding Path=CurrentPrice}"
Style="{StaticResource textStyleTextBlock}"/>
</StackPanel>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Color</src:SpecialFeatures>
</DataTrigger.Value>
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Highlight</src:SpecialFeatures>
</DataTrigger.Value>
<Setter Property="BorderBrush" Value="Orange" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="Visibility" Value="Visible" TargetName="star" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Bei Verwendung dieser beiden Datenvorlagen entspricht die resultierende Benutzeroberfläche der im Abschnitt Was ist Datenbindung? dargestellten Benutzeroberfläche. Wie Sie in diesem Screenshot erkennen können, ermöglichen Datenvorlagen nicht nur das Einfügen von Daten in die Steuerelemente, sondern auch das Definieren überzeugender Grafiken für Ihre Daten. Beispielsweise werden DataTrigger in der obigen DataTemplate verwendet, sodass AuctionItem-Objekte mit HighLight als SpecialFeatures-Wert mit einem orangefarbenen Rahmen und einem Stern angezeigt werden.
Weitere Informationen zu Datenvorlagen finden Sie in der Übersicht über Datenvorlagen.
Datenvalidierung
Die meisten Apps, bei denen Benutzereingaben erfolgen, benötigen Validierungslogik, um sicherzustellen, dass der Benutzer die erwarteten Informationen eingegeben hat. Die Validierungsprüfungen können auf Typ, Bereich, Format oder anderen App-spezifischen Anforderungen basieren. In diesem Abschnitt wird erklärt, wie Datenvalidierung in WPF funktioniert.
Zuordnen von Validierungsregeln zu einer Bindung
Das WPF-Datenbindungsmodell ermöglicht Ihnen die Zuordnung von ValidationRules zum Binding-Objekt. Im folgenden Beispiel wird z. B. einTextBox -Element an eine Eigenschaft mit der Bezeichnung StartPrice
gebunden, und ein ExceptionValidationRule-Objekt wird der Binding.ValidationRules-Eigenschaft hinzugefügt.
<TextBox Name="StartPriceEntryForm" Grid.Row="2"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Ein ValidationRule-Objekt überprüft, ob der Wert einer Eigenschaft gültig ist. WPF verfügt über zwei Typen integrierter ValidationRule-Objekte:
Eine ExceptionValidationRule überprüft, ob während des Updates der Bindungsquelleigenschaft Ausnahmen ausgelöst wurden. Im vorherigen Beispiel ist
StartPrice
eine ganze Zahl. Wenn der Benutzer einen Wert eingibt, der nicht in eine ganze Zahl konvertiert werden kann, wird eine Ausnahme ausgelöst, wodurch die Bindung als ungültig markiert wird. Alternativ zum expliziten Festlegen der ExceptionValidationRule können Sie die ValidatesOnExceptions-Eigenschaft destrue
- oder Binding-Objekts auf MultiBinding festlegen.Ein DataErrorValidationRule-Objekt überprüft, ob Fehler vorliegen, die von Objekten ausgelöst werden, die die IDataErrorInfo-Schnittstelle implementieren. Ein Beispiel für die Verwendung dieser Validierungsregel finden Sie unter DataErrorValidationRule. Alternativ zum expliziten Festlegen der DataErrorValidationRule können Sie die ValidatesOnDataErrors-Eigenschaft des
true
- oder Binding-Objekts auf MultiBinding festlegen.
Sie können auch eigene Validierungsregeln erstellen, indem Sie von der ValidationRule-Klasse ableiten und die Validate-Methode implementieren. Das folgende Beispiel zeigt die Regel, die vom -Element „Start Date“ in TextBox im Abschnitt Was ist Datenbindung? verwendet wird.
public class FutureDateRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
// Test if date is valid
if (DateTime.TryParse(value.ToString(), out DateTime date))
{
// Date is not in the future, fail
if (DateTime.Now > date)
return new ValidationResult(false, "Please enter a date in the future.");
}
else
{
// Date is not a valid date, fail
return new ValidationResult(false, "Value is not a valid date.");
}
// Date is valid and in the future, pass
return ValidationResult.ValidResult;
}
}
Public Class FutureDateRule
Inherits ValidationRule
Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult
Dim inputDate As Date
' Test if date is valid
If Date.TryParse(value.ToString, inputDate) Then
' Date is not in the future, fail
If Date.Now > inputDate Then
Return New ValidationResult(False, "Please enter a date in the future.")
End If
Else
' // Date Is Not a valid date, fail
Return New ValidationResult(False, "Value is not a valid date.")
End If
' Date is valid and in the future, pass
Return ValidationResult.ValidResult
End Function
End Class
Das -Element TextBox verwendet diese FutureDateRule, wie es im folgenden Beispiel gezeigt ist.
<TextBox Name="StartDateEntryForm" Grid.Row="3"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged"
Converter="{StaticResource dateConverter}" >
<Binding.ValidationRules>
<src:FutureDateRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Da der UpdateSourceTrigger-Wert auf PropertyChanged festgelegt ist, aktualisiert die Bindungs-Engine den Quellwert bei jeder Tastatureingabe und überprüft daher auch alle Regeln in der ValidationRules-Auflistung bei jeder Tastatureingabe. Dies wird im Abschnitt zum Validierungsprozess näher erläutert.
Bereitstellen von visuellem Feedback
Wenn der Benutzer einen ungültigen Wert eingibt, kann es sinnvoll sein, Feedback zum Fehler auf der Benutzeroberfläche der App zu geben. Eine Möglichkeit zum Bereitstellen von Feedback besteht darin, die angefügte Eigenschaft Validation.ErrorTemplate auf eine benutzerdefinierte ControlTemplate festzulegen. Wie im vorherigen Unterabschnitt gezeigt, verwendet das -Element TextBox eine ErrorTemplate namens validationTemplate. Im folgenden Beispiel sehen Sie die Definition von validationTemplate.
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
Das AdornedElementPlaceholder-Element gibt an, wo das erweiterte Steuerelement platziert werden soll.
Darüber hinaus können Sie ToolTip verwenden, um die Fehlermeldung anzuzeigen. Die beiden -Elemente StartDateEntryForm und TextBox verwenden das Format textStyleTextBox, das einen ToolTip erstellt, um die Fehlermeldung anzuzeigen. Im folgenden Beispiel wird die Definition von textStyleTextBox dargestellt. Die angefügte Eigenschaft Validation.HasError ist true
, wenn mindestens eine der Bindungen an die Eigenschaften des gebundenen Elements fehlerhaft ist.
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Mit der benutzerdefinierten ErrorTemplate und dem ToolTip sieht das -Element TextBox bei einem Validierungsfehler folgendermaßen aus.
Wenn Ihre Binding über zugeordnete Validierungsregeln verfügt, Sie aber keine ErrorTemplate für das gebundene Steuerelement angeben, wird eine standardmäßige ErrorTemplate verwendet, um Benutzer bei einem Validierungsfehler zu benachrichtigen. Die standardmäßige ErrorTemplate ist eine Steuerelementvorlage, die einen roten Rahmen auf der Adornerebene definiert. Mit der standardmäßigen ErrorTemplate und dem ToolTip sieht das -Element TextBox bei einem Validierungsfehler folgendermaßen aus.
Ein Beispiel zum Bereitstellen von Logik zum Validieren aller Steuerelemente in einem Dialogfeld finden Sie im Abschnitt zu benutzerdefinierten Dialogfeldern in der Übersicht über Dialogfelder.
Validierungsprozess
Eine Validierung erfolgt normalerweise, wenn der Wert eines Ziels an die Bindungsquelleigenschaft übergeben wird. Diese Übergabe erfolgt bei TwoWay- und OneWayToSource-Bindungen. Wodurch die Aktualisierung einer Quelle verursacht wird, hängt also vom Wert der UpdateSourceTrigger-Eigenschaft ab, wie im Abschnitt Wodurch werden Quellaktualisierungen ausgelöst? beschrieben.
Im Folgenden wird der Prozess der Validierung beschrieben. Der Prozess wird bei Auftreten eines Validierungs- oder anderen Fehlers in seinem Verlauf angehalten:
Die Bindungs-Engine überprüft, ob benutzerdefinierte ValidationRule-Objekte definiert sind, deren ValidationStep auf RawProposedValue für die Binding festgelegt ist. In diesem Fall wird die Validate-Methode für jede ValidationRule aufgerufen, bis eine der Regeln einen Fehler zurückgibt oder alle Regeln erfolgreich überprüft wurden.
Anschließend ruft die Bindungs-Engine den Konverter auf, sofern vorhanden.
Bei erfolgreicher Ausführung des Konverters überprüft die Bindungs-Engine, ob benutzerdefinierte ValidationRule-Objekte definiert sind, deren ValidationStep auf ConvertedProposedValue für die Binding festgelegt ist. In diesem Fall wird die Validate-Methode für jede ValidationRule aufgerufen, für die ValidationStep auf ConvertedProposedValue festgelegt ist, bis eine der Regeln einen Fehler zurückgibt oder alle Regeln erfolgreich überprüft wurden.
Die Bindungs-Engine legt die Quelleigenschaft fest.
Die Bindungs-Engine überprüft, ob benutzerdefinierte ValidationRule-Objekte definiert sind, deren ValidationStep auf UpdatedValue für die Binding festgelegt ist. In diesem Fall wird die Validate-Methode für jede ValidationRule aufgerufen, für die ValidationStep auf UpdatedValue festgelegt ist, bis eine der Regeln einen Fehler zurückgibt oder alle Regeln erfolgreich überprüft wurden. Wenn ein DataErrorValidationRule-Objekt einer Bindung zugeordnet und das ValidationStep-Element des Objekts auf den Standardwert (UpdatedValue) festgelegt ist, wird an dieser Stelle das DataErrorValidationRule-Objekt geprüft. An diesem Punkt wird jede Bindung, für die ValidatesOnDataErrors auf
true
festgelegt ist, geprüft.Die Bindungs-Engine überprüft, ob benutzerdefinierte ValidationRule-Objekte definiert sind, deren ValidationStep auf CommittedValue für die Binding festgelegt ist. In diesem Fall wird die Validate-Methode für jede ValidationRule aufgerufen, für die ValidationStep auf CommittedValue festgelegt ist, bis eine der Regeln einen Fehler zurückgibt oder alle Regeln erfolgreich überprüft wurden.
Wenn während dieses Prozesses eine ValidationRule nicht erfolgreich überprüft wird, erstellt die Bindungs-Engine ein ValidationError-Objekt und fügt es der Validation.Errors-Auflistung des gebundenen Elements hinzu. Bevor die Bindungs-Engine die ValidationRule-Objekte im Rahmen eines Schritts ausführt, werden alle ValidationError entfernt, die der angefügten Eigenschaft Validation.Errors des gebundenen Elements während dieses Schritts hinzugefügt wurden. Wenn beispielsweise eine ValidationRule, deren ValidationStep auf UpdatedValue festgelegt ist, fehlgeschlagen ist, entfernt die Bindungs-Engine bei der nächsten Ausführung des Validierungsprozesses diesen ValidationError unmittelbar vor dem Aufruf einer ValidationRule, für die ValidationStep auf UpdatedValue festgelegt ist.
Wenn Validation.Errors nicht leer ist, wird die angefügte Eigenschaft Validation.HasError des Elements auf true
festgelegt. Wenn außerdem die NotifyOnValidationError-Eigenschaft der Binding auf true
festgelegt ist, löst die Bindungs-Engine das angefügte Ereignis Validation.Error des Elements aus.
Beachten Sie außerdem, dass durch eine gültige Wertübertragung (Ziel zu Quelle oder umgekehrt) die angefügte Eigenschaft Validation.Errors gelöscht wird.
Wenn der Bindung entweder ein ExceptionValidationRule-Element zugeordnet ist oder die ValidatesOnExceptions-Eigenschaft der Bindung auf true
festgelegt wurde und beim Festlegen der Quelle durch die Bindungs-Engine eine Ausnahme ausgelöst wird, überprüft die Bindungs-Engine, ob ein UpdateSourceExceptionFilter-Element vorhanden ist. Sie können den UpdateSourceExceptionFilter-Rückruf verwenden, um einen benutzerdefinierten Handler zum Behandeln von Ausnahmen bereitzustellen. Wenn kein UpdateSourceExceptionFilter für die Binding angegeben ist, erstellt die Bindungs-Engine einen ValidationError mit der Ausnahme und fügt ihn der Validation.Errors-Auflistung des gebundenen Elements hinzu.
Debugverfahren
Sie können die angefügte Eigenschaft PresentationTraceSources.TraceLevel auf ein für die Datenbindung relevantes Objekt festlegen, um Informationen über den Status einer bestimmten Bindung zu erhalten.
Siehe auch
.NET Desktop feedback