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 BindableObject
klasy . 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 StringFormat
metody . 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:
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 BindingContext
obiekt , 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 docelowegoOneWayToSource
— wartości są przesyłane z miejsca docelowego do źródłaTwoWay
— wartości są transferowane na oba sposoby między źródłem a obiektem docelowymOneTime
— dane przechodzą ze źródła do miejsca docelowego, ale tylko wtedy, gdyBindingContext
zmiany
Poniższy program demonstruje jedno typowe użycie trybów OneWayToSource
i TwoWay
powiązania. Cztery Slider
widoki mają kontrolować Scale
właściwości Label
, Rotate
, RotateX
i 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 Slider
element . 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ą Scale
właściwościami , Rotate
, RotateX
i 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
, RotateX
i 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.
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
ListView
ItemsSource
definiuje właściwość typu IEnumerable
i 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 Cell
klasy . 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 string
i 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
:
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 DataTemplate
ViewCell
elementu . 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ą ViewCell
zawartości .) Ten znacznik wyświetla FriendlyName
właściwość każdego NamedColor
obiektu:
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 R
właściwości Xamarin.FormsColor
struktury , G
i 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:
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. ObservableCollection
implementuje 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.