Udostępnij za pośrednictwem


Część 4. Powiązania danych — podstawy

Powiązania danych umożliwiają połączenie właściwości dwóch obiektów, dzięki czemu zmiana w jednym powoduje zmianę w drugiej. Jest to bardzo cenne narzędzie, a powiązania danych można zdefiniować całkowicie w kodzie, język XAML udostępnia skróty i wygodę. W związku z tym jednym z najważniejszych rozszerzeń znaczników w pliku Xamarin.Forms jest Powiązanie.

Powiązania danych

Powiązania danych łączą właściwości dwóch obiektów, nazywanych źródłem i obiektem docelowym. W kodzie wymagane są dwa kroki: BindingContext właściwość obiektu docelowego musi być ustawiona na obiekt źródłowy, a SetBinding metoda (często używana w połączeniu z Binding klasą) musi być wywoływana na obiekcie docelowym, aby powiązać właściwość tego obiektu z właściwością obiektu źródłowego.

Właściwość docelowa musi być właściwością powiązaną, co oznacza, że obiekt docelowy musi pochodzić z BindableObjectklasy . Dokumentacja online Xamarin.Forms wskazuje, które właściwości są właściwościami, które można powiązać. Właściwość Label , taka jak Text , jest skojarzona z właściwością powiązaną TextProperty.

W adiustacji należy również wykonać te same dwa kroki, które są wymagane w kodzie, z tą różnicą, że Binding rozszerzenie znaczników zajmuje miejsce SetBinding wywołania i Binding klasy.

Jednak podczas definiowania powiązań danych w języku XAML istnieje wiele sposobów ustawiania BindingContext obiektu docelowego. Czasami jest on ustawiany z pliku za kodem, czasami przy użyciu StaticResource rozszerzenia lub x:Static znaczników, a czasami jako zawartość tagów BindingContext właściwości-elementu.

Powiązania są najczęściej używane do łączenia wizualizacji programu z bazowym modelem danych, zwykle w realizacji architektury aplikacji MVVM (Model-View-ViewModel), jak opisano w części 5. Z powiązań danych do MVVM, ale możliwe są inne scenariusze.

Powiązania widok-widok

Powiązania danych można zdefiniować, aby połączyć właściwości dwóch widoków na tej samej stronie. W takim przypadku należy ustawić BindingContext obiekt docelowy przy użyciu x:Reference rozszerzenia znaczników.

Oto plik XAML zawierający Slider dwa Label widoki i, z których jeden jest obracany przez Slider wartość, a drugi, który wyświetla Slider wartość:

<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>

x:Name Zawiera Slider atrybut, do którego odwołuje się dwa Label widoki przy użyciu x:Reference rozszerzenia znaczników.

x:Reference Rozszerzenie powiązania definiuje właściwość o nazwie Name , aby ustawić nazwę elementu, do którego odwołuje się odwołanie, w tym przypadku slider. ReferenceExtension Jednak klasa, która definiuje x:Reference rozszerzenie znaczników, definiuje ContentProperty również atrybut dla Name, co oznacza, że nie jest jawnie wymagane. Tylko dla odmiany pierwszy x:Reference zawiera ciąg "Name=", ale drugi nie:

BindingContext="{x:Reference Name=slider}"
…
BindingContext="{x:Reference slider}"

Rozszerzenie Binding znaczników może mieć kilka właściwości, podobnie jak klasa BindingBase i Binding . Element for ContentProperty Binding to Path, ale część "Path=" rozszerzenia znaczników może zostać pominięta, jeśli ścieżka jest pierwszym elementem Binding w rozszerzeniu znaczników. Pierwszy przykład zawiera ciąg "Path=", ale drugi przykład pomija go:

Rotation="{Binding Path=Value}"
…
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

Właściwości mogą znajdować się w jednym wierszu lub rozdzielić je na wiele wierszy:

Text="{Binding Value,
               StringFormat='The angle is {0:F0} degrees'}"

Zrób to, co jest wygodne.

Zwróć uwagę na StringFormat właściwość w drugim Binding rozszerzeniu znaczników. W Xamarin.Formssystemie powiązania nie wykonują żadnych niejawnych konwersji typów, a jeśli chcesz wyświetlić obiekt inny niż ciąg jako ciąg, musisz podać konwerter typów lub użyć polecenia StringFormat. W tle metoda statyczna String.Format jest używana do implementowania StringFormatmetody . Jest to potencjalnie problem, ponieważ specyfikacje formatowania platformy .NET obejmują nawiasy klamrowe, które są również używane do rozdzielania rozszerzeń znaczników. Stwarza to ryzyko mylące analizatora XAML. Aby tego uniknąć, umieść cały ciąg formatowania w pojedynczych cudzysłowach:

Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

Oto uruchomiony program:

Powiązania widok-widok

Tryb powiązania

Pojedynczy widok może mieć powiązania danych z kilkoma jego właściwościami. Jednak każdy widok może mieć tylko jeden BindingContextobiekt , więc wiele powiązań danych w tym widoku musi zawierać wszystkie właściwości odwołania tego samego obiektu.

Rozwiązaniem tego i innych problemów jest Mode właściwość , która jest ustawiona na element członkowski BindingMode wyliczenia:

  • Default
  • OneWay — wartości są przesyłane ze źródła do miejsca docelowego
  • OneWayToSource — wartości są przesyłane z miejsca docelowego do źródła
  • TwoWay — wartości są transferowane na oba sposoby między źródłem a obiektem docelowym
  • OneTime — dane przechodzą ze źródła do miejsca docelowego, ale tylko wtedy, gdy BindingContext zmiany

Poniższy program demonstruje jedno typowe użycie trybów OneWayToSource i TwoWay powiązania. Cztery Slider widoki mają kontrolować Scalewłaściwości Label, Rotate, RotateXi RotateY . Na początku wydaje się, że te cztery właściwości Label powinny być obiektami docelowymi powiązania danych, ponieważ każdy z nich jest ustawiany przez Sliderelement . Może BindingContext Label to być jednak tylko jeden obiekt i istnieją cztery różne suwaki.

Z tego powodu wszystkie powiązania są ustawiane na pozornie wstecz: każdy BindingContext z czterech suwaków jest ustawiony na Label, a powiązania są ustawione na Value właściwości suwaków. Za pomocą OneWayToSource trybów i TwoWay te Value właściwości mogą ustawiać właściwości źródłowe, które są Scalewłaściwościami , Rotate, RotateXi RotateY :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>

Powiązania trzech widoków Slider to OneWayToSource, co oznacza, że Slider wartość powoduje zmianę właściwości jej BindingContext, która jest Label nazwą label. Te trzy Slider widoki powodują zmiany właściwości Rotate, RotateXi RotateY .Label

Jednak powiązanie właściwości Scale to TwoWay. Jest to spowodowane tym, że Scale właściwość ma wartość domyślną 1, a użycie TwoWay powiązania powoduje Slider ustawienie wartości początkowej na 1, a nie 0. Gdyby to powiązanie było OneWayToSource, Scale właściwość początkowo zostałaby ustawiona na wartość 0 z wartości domyślnej Slider . Element Label nie będzie widoczny i może to spowodować pewne zamieszanie dla użytkownika.

Powiązania wsteczne

Uwaga

Klasa VisualElement ma ScaleX również właściwości i ScaleY , które są skalowane VisualElement odpowiednio na osi x i y.

Powiązania i kolekcje

Nic nie ilustruje możliwości XAML i powiązań danych lepiej niż szablon .ListView

ListViewItemsSource definiuje właściwość typu IEnumerablei wyświetla elementy w tej kolekcji. Te elementy mogą być obiektami dowolnego typu. Domyślnie ListView do wyświetlania tego elementu jest używana ToString metoda każdego elementu. Czasami jest to tylko to, ToString czego potrzebujesz, ale w wielu przypadkach zwraca tylko w pełni kwalifikowaną nazwę klasy obiektu.

Jednak elementy w ListView kolekcji mogą być wyświetlane w dowolny sposób za pomocą szablonu, który obejmuje klasę pochodzącą z Cellklasy . Szablon jest klonowany dla każdego elementu w elemencie ListView, a powiązania danych ustawione na szablon są przenoszone do poszczególnych klonów.

Bardzo często należy utworzyć komórkę niestandardową dla tych elementów przy użyciu ViewCell klasy . Ten proces jest nieco niechlujny w kodzie, ale w języku XAML staje się bardzo prosty.

W projekcie XamlSamples znajduje się klasa o nazwie NamedColor. Każdy NamedColor obiekt ma Name właściwości typu stringi FriendlyName oraz Color właściwość typu Color. Ponadto zawiera NamedColor 141 statycznych pól typu Color tylko do odczytu odpowiadających kolorom zdefiniowanym Xamarin.FormsColor w klasie. Konstruktor statyczny tworzy kolekcję zawierającą IEnumerable<NamedColor> NamedColor obiekty odpowiadające tym polam statycznym i przypisuje ją do jej publicznej właściwości statycznej All .

Ustawienie właściwości statycznej NamedColor.All na element ItemsSource ListView jest łatwe przy użyciu x:Static rozszerzenia znaczników:

<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>

Wynikowy ekran określa, że elementy są naprawdę typu XamlSamples.NamedColor:

Wiązanie z kolekcją

Nie jest to wiele informacji, ale ListView można je przewijać i wybierać.

Aby zdefiniować szablon dla elementów, należy podzielić ItemTemplate właściwość jako element właściwości i ustawić go na wartość , która następnie odwołuje się do DataTemplateViewCellelementu . View Do właściwości obiektu ViewCell można zdefiniować układ jednego lub większej liczby widoków, aby wyświetlić każdy element. Oto prosty przykład:

<ListView ItemsSource="{x:Static local:NamedColor.All}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <ViewCell.View>
                    <Label Text="{Binding FriendlyName}" />
                </ViewCell.View>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Uwaga

Źródłem powiązania dla komórek i elementów podrzędnych komórek jest ListView.ItemsSource kolekcja.

Element Label jest ustawiony na View właściwość ViewCell. (Tagi ViewCell.View nie są potrzebne, ponieważ View właściwość jest właściwością ViewCellzawartości .) Ten znacznik wyświetla FriendlyName właściwość każdego NamedColor obiektu:

Wiązanie z kolekcją za pomocą elementu DataTemplate

O wiele lepiej. Teraz wszystko, co jest potrzebne, to sprucenie szablonu elementu z więcej informacji i rzeczywistego koloru. Aby obsługiwać ten szablon, niektóre wartości i obiekty zostały zdefiniowane w słowniku zasobów strony:

<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>

Zwróć uwagę na użycie metody OnPlatform , aby zdefiniować rozmiar i BoxView wysokość ListView wierszy. Chociaż wartości dla wszystkich platform są takie same, znaczniki można łatwo dostosować do innych wartości, aby dostosować ekran.

Konwertery wartości powiązania

Poprzedni plik XAML Demo ListView wyświetla poszczególne Rwłaściwości Xamarin.FormsColor struktury , Gi B . Te właściwości są typu double i wahają się od 0 do 1. Jeśli chcesz wyświetlić wartości szesnastkowe, nie można po prostu używać StringFormat ze specyfikacją formatowania "X2". To działa tylko dla liczb całkowitych, double a poza tym wartości muszą być mnożone przez 255.

Ten mały problem został rozwiązany za pomocą konwertera wartości, nazywanego również konwerterem powiązań. Jest to klasa, która implementuje IValueConverter interfejs, co oznacza, że ma dwie metody o nazwie Convert i ConvertBack. Metoda Convert jest wywoływana, gdy wartość jest transferowana ze źródła do obiektu docelowego; ConvertBack metoda jest wywoływana do transferów z lokalizacji docelowej do źródła w OneWayToSource lub TwoWay powiązań:

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;
        }
    }
}

Metoda ConvertBack nie odgrywa roli w tym programie, ponieważ powiązania są tylko jednym ze sposobów od źródła do celu.

Powiązanie odwołuje się do konwertera powiązań z właściwością Converter . Konwerter powiązań może również zaakceptować parametr określony za pomocą ConverterParameter właściwości . W przypadku pewnej wszechstronności jest to sposób określenia mnożnika. Konwerter powiązań sprawdza parametr konwertera dla prawidłowej double wartości.

Konwerter jest tworzone w słowniku zasobów, dzięki czemu może być współużytkowany między wieloma powiązaniami:

<local:DoubleToIntConverter x:Key="intConverter" />

Trzy powiązania danych odwołują się do tego pojedynczego wystąpienia. Zwróć uwagę, że Binding rozszerzenie znaczników zawiera osadzone StaticResource rozszerzenie znaczników:

<Label Text="{Binding Color.R,
                      Converter={StaticResource intConverter},
                      ConverterParameter=255,
                      StringFormat='R={0:X2}'}" />

Oto wynik:

Wiązanie z kolekcją za pomocą elementu DataTemplate i konwerterów

Jest to dość wyrafinowane ListView w obsłudze zmian, które mogą dynamicznie występować w danych bazowych, ale tylko w przypadku podjęcia pewnych kroków. Jeśli kolekcja elementów przypisanych ListView do ItemsSource właściwości zmian w czasie wykonywania , oznacza to, że jeśli elementy można dodać lub usunąć z kolekcji, użyj ObservableCollection klasy dla tych elementów. ObservableCollectionimplementuje interfejs i ListView zainstaluje INotifyCollectionChanged program obsługi dla CollectionChanged zdarzenia.

Jeśli właściwości elementów zmieniają się podczas wykonywania, elementy w kolekcji powinny implementować INotifyPropertyChanged interfejs i sygnalizować zmiany wartości właściwości przy użyciu PropertyChanged zdarzenia. Jest to pokazane w następnej części tej serii, część 5. Z powiązania danych do MVVM.

Podsumowanie

Powiązania danych zapewniają zaawansowany mechanizm łączenia właściwości między dwoma obiektami na stronie lub między obiektami wizualnymi i podstawowymi danymi. Jednak gdy aplikacja zaczyna pracować ze źródłami danych, popularny wzorzec architektury aplikacji zaczyna pojawiać się jako przydatny paradygmat. Opisano to w części 5. Z powiązań danych do MVVM.