Teil 4. Grundlagen der Datenbindung
Datenbindungen ermöglichen es, Eigenschaften von zwei Objekten zu verknüpfen, sodass eine Änderung in einem Objekt zu einer Änderung im anderen führt. Dies ist ein sehr wertvolles Tool, und während Datenbindungen vollständig im Code definiert werden können, bietet XAML Verknüpfungen und Benutzerfreundlichkeit. Folglich ist eine der wichtigsten Markuperweiterungen in Xamarin.Forms Bindung.
Datenbindungen
Datenbindungen verbinden Eigenschaften von zwei Objekten, die als Quelle und Ziel bezeichnet werden. 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 bindbare Eigenschaft sein, was bedeutet, dass das Zielobjekt von BindableObject
abgeleitet werden muss. Die Onlinedokumentation Xamarin.Forms gibt an, welche Eigenschaften bindbare Eigenschaften sind. Eine -Eigenschaft von Label
wie Text
ist der bindbaren Eigenschaft TextProperty
zugeordnet.
Im Markup müssen Sie auch die gleichen zwei Schritte ausführen, die im Code erforderlich sind, mit der Ausnahme, dass die Binding
Markuperweiterung den -Aufruf und die Binding
-Klasse übernimmtSetBinding
.
Wenn Sie Jedoch Datenbindungen in XAML definieren, gibt es mehrere Möglichkeiten, die des BindingContext
Zielobjekts festzulegen. Manchmal wird es aus der CodeBehind-Datei festgelegt, manchmal mit einer StaticResource
Markuperweiterung oder x:Static
und manchmal als Inhalt von BindingContext
Eigenschaftselementtags.
Bindungen werden am häufigsten verwendet, um die Visuals eines Programms mit einem zugrunde liegenden Datenmodell zu verbinden, in der Regel in einer Umsetzung der MVVM-Anwendungsarchitektur (Model-View-ViewModel), wie in Teil 5 erläutert. Von Datenbindungen zu MVVM, aber es sind andere Szenarien 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 legen Sie die BindingContext
des Zielobjekts mithilfe der x:Reference
Markuperweiterung fest.
Hier sehen Sie eine XAML-Datei, die eine Slider
und zwei Label
Ansichten enthält, von denen eine 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>
Slider
enthält ein x:Name
Attribut, auf das die beiden Label
Sichten mithilfe der x:Reference
Markuperweiterung verweisen.
Die x:Reference
Bindungserweiterung definiert eine Eigenschaft namens Name
, die auf den Namen des Elements festgelegt wird, auf das verwiesen wird, in diesem Fall slider
. Die Klasse, die ReferenceExtension
die x:Reference
Markuperweiterung definiert, definiert jedoch auch ein ContentProperty
Attribut für Name
, was bedeutet, dass es nicht explizit erforderlich ist. Nur für Die Sorte enthält die erste x:Reference
"Name=", aber die zweite nicht:
BindingContext="{x:Reference Name=slider}"
…
BindingContext="{x:Reference slider}"
Die Binding
Markuperweiterung selbst kann mehrere Eigenschaften aufweisen, genau wie die - und Binding
-BindingBase
Klasse. Der ContentProperty
für Binding
ist Path
, aber der "Path="-Teil der Markuperweiterung kann weggelassen werden, wenn der Pfad das erste Element in der Binding
Markuperweiterung ist. Das erste Beispiel hat "Path=", 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 unterteilt werden:
Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"
Tun Sie, was bequem ist.
Beachten Sie die StringFormat
-Eigenschaft in der zweiten Binding
Markuperweiterung. In Xamarin.Formsführen Bindungen keine impliziten Typkonvertierungen durch, und wenn Sie ein Nicht-Zeichenfolgenobjekt als Zeichenfolge anzeigen müssen, müssen Sie einen Typkonverter bereitstellen oder verwenden StringFormat
. Im Hintergrund wird die statische String.Format
Methode verwendet, um zu implementieren StringFormat
. Dies ist möglicherweise ein Problem, da .NET-Formatierungsspezifikationen geschweifte Klammern enthalten, die auch zum Trennen von Markuperweiterungen verwendet werden. Dies führt zu einer Verwechslung des XAML-Parsers. Um dies zu vermeiden, setzen Sie die gesamte Formatierungszeichenfolge in einfache Anführungszeichen:
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Hier sehen Sie das ausgeführte Programm:
Der Bindungsmodus
Eine einzelne Ansicht kann Datenbindungen für mehrere eigenschaften aufweisen. Jede Ansicht kann jedoch nur eine BindingContext
enthalten, sodass mehrere Datenbindungen für diese Ansicht alle auf Eigenschaften desselben Objekts verweisen müssen.
Die Lösung für dieses und andere Probleme umfasst die Mode
-Eigenschaft, die auf einen Member der BindingMode
-Enumeration festgelegt ist:
Default
OneWay
— Werte werden von der Quelle auf das Ziel übertragen.OneWayToSource
— Werte werden vom Ziel auf die Quelle übertragen.TwoWay
— Werte werden in beiden Richtungen zwischen Quelle und Ziel übertragen.OneTime
– Daten werden von Quelle zu Ziel, aber nur, wenn sich dieBindingContext
daten ändern.
Das folgende Programm veranschaulicht eine häufige Verwendung der OneWayToSource
Bindungsmodi und TwoWay
. Vier Slider
Ansichten sollen die Scale
Eigenschaften , Rotate
, RotateX
und RotateY
eines Label
steuern. Zunächst scheint es, als ob diese vier Eigenschaften von Label
Datenbindungsziele sein sollten, da jede von einem Slider
festgelegt wird. Der BindingContext
von Label
kann jedoch nur ein Objekt sein, und es gibt vier verschiedene Schieberegler.
Aus diesem Grund werden alle Bindungen scheinbar rückwärts festgelegt: Die BindingContext
der vier Schieberegler wird auf Label
festgelegt, und die Bindungen werden für die Value
Eigenschaften der Schieberegler festgelegt. Mithilfe der OneWayToSource
Modi und TwoWay
können diese Value
Eigenschaften die Quelleigenschaften festlegen, die die Scale
Eigenschaften , Rotate
, RotateX
und RotateY
von Label
sind:
<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 für drei der Slider
Ansichten sind OneWayToSource
, was bedeutet, dass der Slider
Wert eine Änderung in der -Eigenschaft von BindingContext
verursacht, die den Label
Namen hat label
. Diese drei Slider
Ansichten verursachen Änderungen an den Rotate
Eigenschaften , RotateX
und RotateY
von Label
.
Die Bindung für die Scale
-Eigenschaft ist TwoWay
jedoch . Dies liegt daran, dass die Scale
Eigenschaft den Standardwert 1 aufweist, und die Verwendung einer TwoWay
Bindung bewirkt, dass der Slider
Anfangswert auf 1 statt auf 0 festgelegt wird. Wenn diese Bindung wäre OneWayToSource
, würde die Scale
-Eigenschaft vom Standardwert auf 0 Slider
festgelegt. Die Label
wäre nicht sichtbar, und dies kann für den Benutzer einige Verwirrung verursachen.
Hinweis
Die VisualElement
-Klasse verfügt auch über ScaleX
eigenschaften und ScaleY
, die die VisualElement
auf der x-Achse bzw. y-Achse skalieren.
Bindungen und Auflistungen
Nichts veranschaulicht die Leistungsfähigkeit von XAML und Datenbindungen besser als ein vorlagenbasiertes ListView
.
ListView
definiert eine ItemsSource
Eigenschaft vom Typ IEnumerable
und zeigt die Elemente in dieser Auflistung an. Diese Elemente können Objekte eines beliebigen Typs sein. Standardmäßig verwendet die ToString
-Methode jedes Elements, ListView
um dieses Element anzuzeigen. Manchmal ist dies genau das, was Sie möchten, aber in vielen Fällen ToString
gibt nur den vollqualifizierten Klassennamen des Objekts zurück.
Die Elemente in der ListView
Auflistung können jedoch beliebig angezeigt werden, indem eine Vorlage verwendet wird, die eine klasse umfasst, die von abgeleitet wird Cell
. Die Vorlage wird für jedes Element in geklont ListView
, und Datenbindungen, die für die Vorlage festgelegt wurden, werden an die einzelnen Klone übertragen.
Häufig sollten Sie mithilfe der ViewCell
-Klasse eine benutzerdefinierte Zelle für diese Elemente erstellen. Dieser Prozess ist im Code etwas unübersichtlich, aber in XAML wird er sehr einfach.
Im XamlSamples-Projekt ist eine Klasse namens NamedColor
enthalten. Jedes NamedColor
Objekt verfügt Name
über eigenschaften und FriendlyName
vom Typ string
sowie eine Color
-Eigenschaft vom Typ Color
. Verfügt darüber hinaus NamedColor
über 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 den ItemsSource
eines ListView
ist mit der x:Static
Markuperweiterung einfach:
<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 tatsächlich vom Typ XamlSamples.NamedColor
sind:
Es sind nicht viele Informationen, aber die ListView
ist scrollbar und auswählbar.
Um eine Vorlage für die Elemente zu definieren, sollten Sie die ItemTemplate
-Eigenschaft als Eigenschaftselement aufteilen und auf einen DataTemplate
festlegen, der dann auf verweist ViewCell
. Für die View
-Eigenschaft von ViewCell
können Sie ein Layout von einer oder mehreren 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
Auflistung.
Das Label
-Element wird auf die View
-Eigenschaft von ViewCell
festgelegt. (Die ViewCell.View
Tags werden nicht benötigt, da die View
-Eigenschaft die Inhaltseigenschaft von ViewCell
ist.) Dieses Markup zeigt die FriendlyName
-Eigenschaft jedes NamedColor
Objekts an:
Viel besser. Jetzt müssen Sie nur noch die Elementvorlage mit weiteren Informationen und der tatsächlichen Farbe auffüllen. 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 von OnPlatform
, um die Größe von und BoxView
die Höhe der ListView
Zeilen zu definieren. Obwohl die Werte für alle Plattformen identisch sind, kann das Markup problemlos für andere Werte angepasst werden, um die Anzeige zu optimieren.
Binden von Wertkonvertern
In der vorherigen ListView-Demo-XAML-Datei werden die einzelnen R
Eigenschaften , G
und B
der Xamarin.FormsColor
-Struktur angezeigt. Diese Eigenschaften sind vom Typ double
und reichen von 0 bis 1. Wenn Sie die Hexadezimalwerte anzeigen möchten, können Sie nicht einfach mit einer "X2"-Formatierungsspezifikation verwenden StringFormat
. Dies 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, auch Bindungskonverter genannt, gelöst. Dies ist eine Klasse, die die IValueConverter
-Schnittstelle implementiert, was bedeutet, dass sie über zwei Methoden mit dem Namen Convert
und ConvertBack
verfügt. Die Convert
-Methode wird aufgerufen, wenn ein Wert von der Quelle zum Ziel übertragen wird. Die ConvertBack
-Methode wird für Übertragungen vom Ziel zur Quelle in OneWayToSource
oder TwoWay
für 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 der Quelle zum Ziel sind.
Eine Bindung verweist mit der Converter
-Eigenschaft auf einen Bindungskonverter. Ein Bindungskonverter kann auch einen parameter akzeptieren, der mit der ConverterParameter
-Eigenschaft angegeben ist. Für eine gewisse Vielseitigkeit wird der Multiplikator so angegeben. 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 instance. 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:
Der ListView
ist bei der Behandlung von Änderungen, die dynamisch in den zugrunde liegenden Daten auftreten können, recht ausgeklügelt, aber nur, wenn Sie bestimmte Schritte ausführen. Wenn die Auflistung von Elementen, die der ItemsSource
-Eigenschaft der -Eigenschaft zugewiesen sind, während der ListView
Laufzeit geändert wird, 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 INotifyCollectionChanged
-Schnittstelle und ListView
installiert einen Handler für das CollectionChanged
-Ereignis.
Wenn sich die Eigenschaften der Elemente selbst während der Laufzeit ändern, sollten die Elemente in der Auflistung die INotifyPropertyChanged
Schnittstelle implementieren und Änderungen an Eigenschaftswerten mithilfe des -Ereignisses PropertyChanged
signalisieren. Dies wird im nächsten Teil dieser Reihe, Teil 5, veranschaulicht. Von der Datenbindung zu MVVM.
Zusammenfassung
Datenbindungen bieten einen leistungsstarken Mechanismus zum Verknüpfen von Eigenschaften zwischen zwei Objekten innerhalb einer Seite oder zwischen visuellen Objekten und zugrunde liegenden Daten. Wenn die Anwendung jedoch beginnt, mit Datenquellen zu arbeiten, entsteht ein gängiges Anwendungsarchitekturmuster als nützliches Paradigma. Dies wird in Teil 5 behandelt. Von Datenbindungen zu MVVM.