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 TextPropertyzugeordnet 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 StringFormatverwendet. 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:

View-to-View-Bindungen

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 sich BindingContext ändert

Das folgende Programm veranschaulicht eine gemeinsame Verwendung der OneWayToSource Modi und TwoWay Bindung. Vier Slider Ansichten sollen die ScaleEigenschaften Rotate, , , RotateXund RotateY Eigenschaften eines Steuerelements Labelsteuern. 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 LabelEigenschaften 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 ScaleEigenschaften , Rotate, und RotateXRotateY 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.

Rückwärtsbindungen

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 stringund 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 ListViewItemsSource 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.NamedColorsind:

Binden an eine Auflistung

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:

Binden an eine Auflistung mit einer DataTemplate

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, Gund 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:

Binden an eine Auflistung mit einer DataTemplate und Konverter

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.