Teil 4. Grundlagen der Datenbindung
Datenbindungen ermöglichen das Verknüpfen von Eigenschaften von zwei Objekten, sodass eine Änderung in einem Objekt zu einer Änderung in der anderen führt. Dies ist ein sehr wertvolles Werkzeug, und während Datenbindungen vollständig im Code definiert werden können, bietet XAML Abkürzungen und Komfort. Folglich ist eine der wichtigsten Markuperweiterungen in Xamarin.Forms Binding.
Datenbindungen
Datenbindungen verbinden Eigenschaften von zwei Objekten, der Quelle und dem Ziel. Im Code sind zwei Schritte erforderlich: Die BindingContext
Eigenschaft des Zielobjekts muss auf das Quellobjekt festgelegt werden, und die SetBinding
Methode (häufig in Verbindung mit der Binding
Klasse verwendet) muss für das Zielobjekt aufgerufen werden, um eine Eigenschaft dieses Objekts an eine Eigenschaft des Quellobjekts zu binden.
Die Zieleigenschaft muss eine bindungsfähige Eigenschaft sein, was bedeutet, dass das Zielobjekt von BindableObject
abgeleitet sein muss. Die Onlinedokumentation Xamarin.Forms gibt an, welche Eigenschaften bindungsfähige Eigenschaften sind. Eine Eigenschaft, die Label
der Text
bindungsfähigen Eigenschaft TextProperty
zugeordnet ist.
Im Markup müssen Sie auch dieselben beiden Schritte ausführen, die im Code erforderlich sind, mit der Ausnahme, dass die Binding
Markuperweiterung den Ort des SetBinding
Aufrufs und der Binding
Klasse übernimmt.
Wenn Sie jedoch Datenbindungen in XAML definieren, gibt es mehrere Möglichkeiten, das BindingContext
des Zielobjekts festzulegen. Manchmal wird sie über die Code-Behind-Datei festgelegt, manchmal über eine StaticResource
- oder x:Static
-Markup-Erweiterung und manchmal als Inhalt von BindingContext
-Eigenschaftselement-Tags.
Bindungen werden am häufigsten verwendet, um die visuellen Elemente eines Programms mit einem zugrunde liegenden Datenmodell zu verbinden, in der Regel in einer Realisierung der MVVM (Model-View-ViewModel)-Anwendungsarchitektur, wie in Teil 5 erläutert. Von Datenbindungen an MVVM, aber andere Szenarien sind möglich.
View-to-View-Bindungen
Sie können Datenbindungen definieren, um Eigenschaften von zwei Ansichten auf derselben Seite zu verknüpfen. In diesem Fall setzen Sie das BindingContext
des Zielobjekts mit der Markuperweiterung x:Reference
.
Hier ist eine XAML-Datei, die eine Slider
und zwei Label
Ansichten enthält, von denen einer durch den Slider
Wert gedreht wird und eine andere, die den Slider
Wert anzeigt:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
<Label BindingContext="{x:Reference slider}"
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Die Slider
enthält ein x:Name
-Attribut, auf das die beiden Label
-Ansichten mit der x:Reference
-Auszeichnungserweiterung verweisen.
Die x:Reference
-Bindungserweiterung definiert eine Eigenschaft namens Name
, die auf den Namen des referenzierten Elements gesetzt wird, in diesem Fall slider
. Die ReferenceExtension
-Klasse, die die x:Reference
-Auszeichnungserweiterung definiert, legt jedoch auch ein ContentProperty
-Attribut für Name
fest, was bedeutet, dass es nicht ausdrücklich erforderlich ist. Nur für die Sorte enthält der erste x:Reference
"Name=", aber die zweite nicht:
BindingContext="{x:Reference Name=slider}"
…
BindingContext="{x:Reference slider}"
Die Auszeichnungserweiterung Binding
selbst kann mehrere Eigenschaften haben, genau wie die Klassen BindingBase
und Binding
. Das ContentProperty
für Binding
ist Path
, aber der „Path=“-Teil der Markup-Erweiterung kann weggelassen werden, wenn der Pfad das erste Element in der Binding
Markup-Erweiterung ist. Das erste Beispiel weist "Path=" auf, aber im zweiten Beispiel wird es weggelassen:
Rotation="{Binding Path=Value}"
…
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Die Eigenschaften können alle in einer Zeile oder in mehrere Zeilen getrennt sein:
Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"
Machen Sie alles, was praktisch ist.
Beachten Sie die StringFormat
Eigenschaft in der zweiten Binding
Markuperweiterung. In Xamarin.Forms, Bindungen führen keine impliziten Typkonvertierungen aus, und wenn Sie ein Objekt ohne Zeichenfolge als Zeichenfolge anzeigen müssen, müssen Sie einen Typkonverter bereitstellen oder verwenden StringFormat
. Hinter den Kulissen wird die statische String.Format
Methode zum Implementieren StringFormat
verwendet. Das ist möglicherweise ein Problem, da .NET-Formatierungsspezifikationen geschweifte Klammern umfassen, die auch zum Trennen von Markuperweiterungen verwendet werden. Dadurch entsteht das Risiko, dass der XAML-Parser verwirrend ist. Um dies zu vermeiden, platzieren Sie die gesamte Formatierungszeichenfolge in einfache Anführungszeichen:
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Das folgende Programm wird ausgeführt:
Der Bindungsmodus
Eine einzelne Ansicht kann Datenbindungen für mehrere seiner Eigenschaften aufweisen. Jede Ansicht kann jedoch nur eine BindingContext
haben, sodass mehrere Datenbindungen in dieser Ansicht alle auf Eigenschaften desselben Objekts verweisen müssen.
Die Lösung für dieses und andere Probleme ist die Eigenschaft Mode
, die auf ein Mitglied der Enumeration BindingMode
gesetzt wird:
Default
OneWay
— Werte werden von der Quelle an das Ziel übertragen.OneWayToSource
— Werte werden vom Ziel an die Quelle übertragen.TwoWay
— Werte werden auf beide Arten zwischen Quelle und Ziel übertragen.OneTime
– Daten gehen von der Quelle zum Ziel, aber nur wenn sichBindingContext
ändert
Das folgende Programm veranschaulicht eine gemeinsame Verwendung der OneWayToSource
Modi und TwoWay
Bindung. Vier Slider
Ansichten sollen die Scale
Eigenschaften Rotate
, , , RotateX
und RotateY
Eigenschaften eines Steuerelements Label
steuern. Auf den ersten Blick scheint es, als ob diese 4 Eigenschaften des Label
datenbindende Ziele sein sollten, da jede von ihnen von einem Slider
festgelegt wird. Die BindingContext
von Label
kann jedoch nur ein Objekt sein, und es gibt 4 verschiedene Schieberegler.
Aus diesem Grund werden alle Bindungen scheinbar rückwärts festgelegt: Die BindingContext
einzelnen Schieberegler werden auf die Label
Eigenschaften der Schieberegler festgelegt, und die Bindungen werden für die Value
Eigenschaften der Schieberegler festgelegt. Mithilfe der OneWayToSource
Folgenden und TwoWay
Modi können diese Value
Eigenschaften die Quelleigenschaften festlegen, bei denen es sich um die Scale
Eigenschaften , Rotate
, und RotateX
RotateY
Eigenschaften der Label
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderTransformsPage"
Padding="5"
Title="Slider Transforms Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Scaled and rotated Label -->
<Label x:Name="label"
Text="TEXT"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<!-- Slider and identifying Label for Scale -->
<Slider x:Name="scaleSlider"
BindingContext="{x:Reference label}"
Grid.Row="1" Grid.Column="0"
Maximum="10"
Value="{Binding Scale, Mode=TwoWay}" />
<Label BindingContext="{x:Reference scaleSlider}"
Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
Grid.Row="1" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for Rotation -->
<Slider x:Name="rotationSlider"
BindingContext="{x:Reference label}"
Grid.Row="2" Grid.Column="0"
Maximum="360"
Value="{Binding Rotation, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationSlider}"
Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
Grid.Row="2" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for RotationX -->
<Slider x:Name="rotationXSlider"
BindingContext="{x:Reference label}"
Grid.Row="3" Grid.Column="0"
Maximum="360"
Value="{Binding RotationX, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationXSlider}"
Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
Grid.Row="3" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for RotationY -->
<Slider x:Name="rotationYSlider"
BindingContext="{x:Reference label}"
Grid.Row="4" Grid.Column="0"
Maximum="360"
Value="{Binding RotationY, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationYSlider}"
Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
Grid.Row="4" Grid.Column="1"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
Die Bindungen von 3 der Slider
-Ansichten sind OneWayToSource
, was bedeutet, dass der Slider
-Wert eine Änderung in der Eigenschaft seiner BindingContext
verursacht, die Label
mit dem Namen label
ist. Diese drei Slider
Ansichten verursachen Änderungen an den Rotate
, RotateX
, und RotateY
Eigenschaften der .Label
Die Bindung für die Eigenschaft Scale
ist jedoch TwoWay
. Das liegt daran, dass die Scale
-Eigenschaft einen Standardwert von 1 hat und die Verwendung einer TwoWay
-Bindung dazu führt, dass der Slider
-Ausgangswert auf 1 statt auf 0 gesetzt wird. Wäre diese Bindung OneWayToSource
, würde die Eigenschaft Scale
vom Standardwert Slider
zunächst auf 0 gesetzt werden. Dies Label
wäre nicht sichtbar, und dies kann für den Benutzer zu Verwirrung führen.
Hinweis
Die Klasse VisualElement
hat auch die Eigenschaften ScaleX
und ScaleY
, die die VisualElement
auf der x-Achse bzw. y-Achse skalieren.
Bindungen und Sammlungen
Nichts veranschaulicht die Leistungsfähigkeit von XAML- und Datenbindungen besser als eine Vorlage ListView
.
ListView
definiert eine ItemsSource
-Eigenschaft vom Typ IEnumerable
und zeigt die Elemente in dieser Sammlung an. Diese Elemente können Objekte eines beliebigen Typs sein. Standardmäßig verwendet ListView
die ToString
-Methode für jedes Element, um dieses Element anzuzeigen. Manchmal ist dies genau das, was Sie wollen, aber in vielen Fällen gibt ToString
nur den vollqualifizierten Klassennamen des Objekts zurück.
Die Elemente in der ListView
-Sammlung können jedoch durch die Verwendung einer Vorlage, bei der es sich um eine Klasse handelt, die von Cell
abgeleitet ist, auf beliebige Weise angezeigt werden. Die Vorlage wird für jedes Element in der ListView
geklont, und Datenbindungen, die auf der Vorlage festgelegt wurden, werden auf die einzelnen Klone übertragen.
Sehr häufig möchten Sie mithilfe der ViewCell
Klasse eine benutzerdefinierte Zelle für diese Elemente erstellen. Dieser Prozess ist etwas unübersichtlich im Code, aber in XAML wird er sehr einfach.
Im XamlSamples-Projekt enthalten ist eine Klasse mit dem Namen NamedColor
. Jedes NamedColor
Objekt hat Name
und FriendlyName
Eigenschaften vom Typ string
und eine Color
Eigenschaft vom Typ Color
. Darüber hinaus NamedColor
enthält 141 statische schreibgeschützte Felder vom Typ Color
, die den in der Xamarin.FormsColor
Klasse definierten Farben entsprechen. Ein statischer Konstruktor erstellt eine IEnumerable<NamedColor>
Auflistung, die Objekte enthält NamedColor
, die diesen statischen Feldern entsprechen, und weist sie der öffentlichen statischen All
Eigenschaft zu.
Das Festlegen der statischen NamedColor.All
Eigenschaft auf eine ListView
ItemsSource
ist einfach mit der x:Static
Markuperweiterung:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ListView ItemsSource="{x:Static local:NamedColor.All}" />
</ContentPage>
Die resultierende Anzeige stellt fest, dass die Elemente wirklich vom Typ XamlSamples.NamedColor
sind:
Es handelt sich nicht um viele Informationen, aber es ListView
ist bildlauffähig und auswählbar.
Um eine Vorlage für die Elemente zu definieren, müssen Sie die ItemTemplate
Eigenschaft als Eigenschaftselement aufbrechen und auf ein DataTemplate
, das dann auf eine ViewCell
. View
Zur Eigenschaft der ViewCell
Können Sie ein Layout einer oder mehrerer Ansichten definieren, um jedes Element anzuzeigen. Hier ist ein einfaches Beispiel:
<ListView ItemsSource="{x:Static local:NamedColor.All}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label Text="{Binding FriendlyName}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Hinweis
Die Bindungsquelle für Zellen und untergeordnete Zellen ist die ListView.ItemsSource
-Sammlung.
Das Label
Element wird auf die View
Eigenschaft der ViewCell
. (Die ViewCell.View
Tags sind nicht erforderlich, da die View
Eigenschaft die Inhaltseigenschaft von ViewCell
.) Dieses Markup zeigt die FriendlyName
Eigenschaft der einzelnen NamedColor
Objekte an:
Viel besser. Jetzt ist alles erforderlich, um die Elementvorlage mit weiteren Informationen und der tatsächlichen Farbe zu fichten. Zur Unterstützung dieser Vorlage wurden einige Werte und Objekte im Ressourcenverzeichnis der Seite definiert:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ContentPage.Resources>
<ResourceDictionary>
<OnPlatform x:Key="boxSize"
x:TypeArguments="x:Double">
<On Platform="iOS, Android, UWP" Value="50" />
</OnPlatform>
<OnPlatform x:Key="rowHeight"
x:TypeArguments="x:Int32">
<On Platform="iOS, Android, UWP" Value="60" />
</OnPlatform>
<local:DoubleToIntConverter x:Key="intConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView ItemsSource="{x:Static local:NamedColor.All}"
RowHeight="{StaticResource rowHeight}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="5, 5, 0, 5"
Orientation="Horizontal"
Spacing="15">
<BoxView WidthRequest="{StaticResource boxSize}"
HeightRequest="{StaticResource boxSize}"
Color="{Binding Color}" />
<StackLayout Padding="5, 0, 0, 0"
VerticalOptions="Center">
<Label Text="{Binding FriendlyName}"
FontAttributes="Bold"
FontSize="Medium" />
<StackLayout Orientation="Horizontal"
Spacing="0">
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
<Label Text="{Binding Color.G,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', G={0:X2}'}" />
<Label Text="{Binding Color.B,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', B={0:X2}'}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Beachten Sie die Verwendung der OnPlatform
Definition der Größe einer BoxView
und der Höhe der ListView
Zeilen. Obwohl die Werte für alle Plattformen gleich sind, könnte das Markup leicht an andere Werte angepasst werden, um die Anzeige zu optimieren.
Binden von Wertkonvertern
Die vorherige ListView Demo-XAML-Datei zeigt die einzelnen R
, G
und B
Eigenschaften der Xamarin.FormsColor
Struktur an. Diese Eigenschaften sind vom Typ double
und reichen von 0 bis 1. Wenn Sie die hexadezimalen Werte anzeigen wollen, können Sie nicht einfach StringFormat
mit einer „X2“-Formatierungsangabe verwenden. Das funktioniert nur für ganze Zahlen und außerdem müssen die double
Werte mit 255 multipliziert werden.
Dieses kleine Problem wurde mit einem Wertkonverter gelöst, auch als Bindungskonverter bezeichnet. Dies ist eine Klasse, die die Schnittstelle IValueConverter
implementiert, was bedeutet, dass sie zwei Methoden namens Convert
und ConvertBack
hat. Die Convert
Methode wird aufgerufen, wenn ein Wert von Quelle zu Ziel übertragen wird; die ConvertBack
Methode wird für Übertragungen von Ziel zu Quelle in OneWayToSource
oder TwoWay
Bindungen aufgerufen:
using System;
using System.Globalization;
using Xamarin.Forms;
namespace XamlSamples
{
class DoubleToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double multiplier;
if (!Double.TryParse(parameter as string, out multiplier))
multiplier = 1;
return (int)Math.Round(multiplier * (double)value);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
double divider;
if (!Double.TryParse(parameter as string, out divider))
divider = 1;
return ((double)(int)value) / divider;
}
}
}
Die ConvertBack
Methode spielt in diesem Programm keine Rolle, da die Bindungen nur eine Möglichkeit von Quelle zu Ziel sind.
Eine Bindung verweist auf einen Bindungskonverter mit der Converter
-Eigenschaft. Ein Bindungskonverter kann auch einen mit der Eigenschaft ConverterParameter
angegebenen Parameter akzeptieren. Für eine gewisse Vielseitigkeit wird der Multiplikator auf diese Weise festgelegt. Der Bindungskonverter überprüft den Konverterparameter auf einen gültigen double
-Wert.
Der Konverter wird im Ressourcenwörterbuch instanziiert, sodass er für mehrere Bindungen freigegeben werden kann:
<local:DoubleToIntConverter x:Key="intConverter" />
Drei Datenbindungen verweisen auf diese einzelne Instanz. Beachten Sie, dass die Binding
Markuperweiterung eine eingebettete StaticResource
Markuperweiterung enthält:
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
Dies ist das Ergebnis:
Dies ListView
ist ziemlich komplex bei der Behandlung von Änderungen, die dynamisch in den zugrunde liegenden Daten auftreten können, aber nur, wenn Sie bestimmte Schritte ausführen. Wenn die Auflistung von Elementen, die der ItemsSource
Eigenschaft der Änderungen während der ListView
Laufzeit zugewiesen sind , d. h. wenn Elemente der Auflistung hinzugefügt oder daraus entfernt werden können, verwenden Sie eine ObservableCollection
Klasse für diese Elemente. ObservableCollection
implementiert die Schnittstelle INotifyCollectionChanged
, und ListView
installiert einen Handler für das Ereignis CollectionChanged
.
Wenn sich die Eigenschaften der Elemente selbst während der Laufzeit ändern, dann sollten die Elemente in der Sammlung die INotifyPropertyChanged
-Schnittstelle implementieren und Änderungen der Eigenschaftswerte mit dem PropertyChanged
-Ereignis signalisieren. Dies wird im nächsten Teil dieser Reihe gezeigt, Teil 5. Von der Datenbindung an MVVM.
Zusammenfassung
Datenbindungen bieten einen leistungsstarken Mechanismus zur Verknüpfung von Eigenschaften zwischen zwei Objekten innerhalb einer Seite oder zwischen visuellen Objekten und zugrunde liegenden Daten. Aber wenn die Anwendung mit der Arbeit mit Datenquellen beginnt, beginnt ein beliebtes Anwendungsarchitekturmuster als nützliches Paradigma zu entstehen. Dies wird in Teil 5 behandelt. Von Datenbindungen an MVVM.