Parte 4. Nozioni di base sul data binding
I data binding consentono di collegare le proprietà di due oggetti in modo che una modifica in uno causi una modifica nell'altra. Si tratta di uno strumento molto prezioso e, mentre i data binding possono essere definiti interamente nel codice, XAML fornisce collegamenti e praticità. Di conseguenza, una delle estensioni di markup più importanti in Xamarin.Forms è Binding.
Data binding
I data binding connettono le proprietà di due oggetti, denominati origine e destinazione. Nel codice sono necessari due passaggi: la BindingContext
proprietà dell'oggetto di destinazione deve essere impostata sull'oggetto di origine e il SetBinding
metodo (spesso usato insieme alla Binding
classe ) deve essere chiamato sull'oggetto di destinazione per associare una proprietà di tale oggetto a una proprietà dell'oggetto di origine.
La proprietà di destinazione deve essere una proprietà associabile, il che significa che l'oggetto di destinazione deve derivare da BindableObject
. La documentazione online Xamarin.Forms indica quali proprietà sono proprietà associabili. Una proprietà di Label
, ad esempio Text
, è associata alla proprietà TextProperty
associabile .
Nel markup è anche necessario eseguire gli stessi due passaggi necessari nel codice, ad eccezione del fatto che l'estensione Binding
di markup ha il posto della SetBinding
chiamata e della Binding
classe .
Tuttavia, quando definisci i data binding in XAML, esistono diversi modi per impostare l'oggetto BindingContext
di destinazione. A volte viene impostato dal file code-behind, a volte usando un'estensione StaticResource
di markup o x:Static
e talvolta come contenuto dei tag dell'elemento BindingContext
proprietà.
Le associazioni vengono usate più spesso per connettere gli oggetti visivi di un programma con un modello di dati sottostante, in genere in una realizzazione dell'architettura dell'applicazione MVVM (Model-View-ViewModel), come illustrato nella parte 5. Da data binding a MVVM, ma altri scenari sono possibili.
Associazioni da visualizzazione a visualizzazione
È possibile definire data binding per collegare le proprietà di due visualizzazioni nella stessa pagina. In questo caso, si imposta l'oggetto BindingContext
dell'oggetto di destinazione usando l'estensione x:Reference
di markup.
Ecco un file XAML che contiene una Slider
e due Label
visualizzazioni, una delle quali viene ruotata dal Slider
valore e un'altra che visualizza il Slider
valore:
<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
Contiene un x:Name
attributo a cui fanno riferimento le due Label
viste usando l'estensione di x:Reference
markup.
L'estensione di x:Reference
associazione definisce una proprietà denominata Name
da impostare sul nome dell'elemento a cui si fa riferimento, in questo caso slider
. Tuttavia, la ReferenceExtension
classe che definisce l'estensione x:Reference
di markup definisce anche un ContentProperty
attributo per Name
, il che significa che non è richiesto in modo esplicito. Solo per varietà, il primo x:Reference
include "Name=", ma il secondo non:
BindingContext="{x:Reference Name=slider}"
…
BindingContext="{x:Reference slider}"
L'estensione Binding
di markup stessa può avere diverse proprietà, proprio come la BindingBase
classe e Binding
. per ContentProperty
Binding
è Path
, ma la parte "Path=" dell'estensione di markup può essere omessa se il percorso è il primo elemento nell'estensione Binding
di markup. Il primo esempio ha "Path=", ma il secondo esempio lo omette:
Rotation="{Binding Path=Value}"
…
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Le proprietà possono essere tutte su una riga o separate in più righe:
Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"
Fai qualsiasi cosa sia conveniente.
Si noti la StringFormat
proprietà nella seconda Binding
estensione di markup. In Xamarin.Formsle associazioni non eseguono conversioni implicite di tipi e, se è necessario visualizzare un oggetto non stringa come stringa, è necessario fornire un convertitore di tipi o usare StringFormat
. In background, il metodo statico String.Format
viene usato per implementare StringFormat
. Questo è potenzialmente un problema, perché le specifiche di formattazione .NET comportano parentesi graffe, che vengono usate anche per delimitare le estensioni di markup. In questo modo si rischia di confondere il parser XAML. Per evitare questo problema, inserire l'intera stringa di formattazione tra virgolette singole:
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Ecco il programma in esecuzione:
Modalità di associazione
Una singola visualizzazione può avere data binding su diverse delle relative proprietà. Tuttavia, ogni vista può avere un BindingContext
solo oggetto , quindi più data binding in tale vista devono fare riferimento a tutte le proprietà dello stesso oggetto.
La soluzione a questo e altri problemi comporta la Mode
proprietà , impostata su un membro dell'enumerazione BindingMode
:
Default
OneWay
— i valori vengono trasferiti dall'origine alla destinazioneOneWayToSource
— i valori vengono trasferiti dalla destinazione all'origineTwoWay
— i valori vengono trasferiti in entrambi i modi tra origine e destinazioneOneTime
— i dati passano dall'origine alla destinazione, ma solo quando cambianoBindingContext
Il programma seguente illustra un uso comune delle modalità di OneWayToSource
associazione e TwoWay
. Quattro Slider
viste sono destinate a controllare le Scale
proprietà , Rotate
RotateX
, e RotateY
di un oggetto Label
. Inizialmente, sembra che queste quattro proprietà di Label
siano destinazioni di data binding perché ognuna viene impostata da un oggetto Slider
. Tuttavia, l'oggetto BindingContext
di Label
può essere un solo oggetto e sono presenti quattro dispositivi di scorrimento diversi.
Per questo motivo, tutte le associazioni vengono impostate in modi apparentemente indietro: la BindingContext
proprietà di ognuno dei quattro dispositivi di scorrimento è impostata su Label
e le associazioni vengono impostate sulle Value
proprietà dei dispositivi di scorrimento. Usando le OneWayToSource
modalità eTwoWay
, queste Value
proprietà possono impostare le proprietà di origine, ovvero le Scale
proprietà , Rotate
RotateX
, e RotateY
di 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>
Le associazioni su tre delle Slider
viste sono OneWayToSource
, ovvero il Slider
valore causa una modifica nella proprietà del relativo BindingContext
oggetto , ovvero l'oggetto Label
denominato label
. Queste tre Slider
visualizzazioni causano modifiche alle Rotate
proprietà , RotateX
e RotateY
di Label
.
Tuttavia, l'associazione per la Scale
proprietà è TwoWay
. Il motivo è che la Scale
proprietà ha un valore predefinito pari a 1 e l'uso di un'associazione TwoWay
fa sì che il Slider
valore iniziale venga impostato su 1 anziché su 0. Se tale associazione fosse OneWayToSource
, la Scale
proprietà verrà inizialmente impostata su 0 dal Slider
valore predefinito. l'oggetto Label
non sarebbe visibile e ciò potrebbe causare confusione all'utente.
Nota
La VisualElement
classe dispone ScaleX
inoltre di proprietà e ScaleY
, che ridimensionano rispettivamente l'oggetto VisualElement
sull'asse x e sull'asse y.
Associazioni e raccolte
Nulla illustra la potenza dei data binding e XAML meglio di un oggetto basato su ListView
modelli.
ListView
definisce una ItemsSource
proprietà di tipo IEnumerable
e visualizza gli elementi nell'insieme. Questi elementi possono essere oggetti di qualsiasi tipo. Per impostazione predefinita, ListView
usa il ToString
metodo di ogni elemento per visualizzare l'elemento. A volte si tratta solo di ciò che si desidera, ma in molti casi restituisce ToString
solo il nome completo della classe dell'oggetto.
Tuttavia, gli elementi nella ListView
raccolta possono essere visualizzati in qualsiasi modo tramite l'uso di un modello, che implica una classe che deriva da Cell
. Il modello viene clonato per ogni elemento in ListView
e i data binding impostati nel modello vengono trasferiti ai singoli cloni.
Molto spesso, è necessario creare una cella personalizzata per questi elementi usando la ViewCell
classe . Questo processo è piuttosto disordinato nel codice, ma in XAML diventa molto semplice.
Incluso nel progetto XamlSamples è una classe denominata NamedColor
. Ogni NamedColor
oggetto ha Name
proprietà di tipo string
e FriendlyName
e una Color
proprietà di tipo Color
. Inoltre, NamedColor
include 141 campi statici di sola lettura di tipo Color
corrispondenti ai colori definiti nella Xamarin.FormsColor
classe . Un costruttore statico crea una IEnumerable<NamedColor>
raccolta che contiene NamedColor
oggetti corrispondenti a questi campi statici e la assegna alla relativa proprietà statica pubblica All
.
L'impostazione della proprietà statica NamedColor.All
su ItemsSource
di è ListView
semplice tramite l'estensione x:Static
di markup:
<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>
La visualizzazione risultante stabilisce che gli elementi sono realmente di tipo XamlSamples.NamedColor
:
Non sono molte informazioni, ma è ListView
scorrevole e selezionabile.
Per definire un modello per gli elementi, è necessario suddividere la ItemTemplate
proprietà come elemento di proprietà e impostarla su , DataTemplate
che quindi fa riferimento a .ViewCell
Per la View
proprietà di ViewCell
è possibile definire un layout di una o più visualizzazioni per visualizzare ogni elemento. Ecco un semplice esempio:
<ListView ItemsSource="{x:Static local:NamedColor.All}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label Text="{Binding FriendlyName}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Nota
L'origine di associazione per le celle e gli elementi figlio di celle è la ListView.ItemsSource
raccolta.
L'elemento Label
è impostato sulla View
proprietà dell'oggetto ViewCell
. I ViewCell.View
tag non sono necessari perché la View
proprietà è la proprietà content di ViewCell
. Questo markup visualizza la FriendlyName
proprietà di ogni NamedColor
oggetto:
Molto meglio. A questo punto tutto ciò che serve è quello di sprucere il modello di elemento con altre informazioni e il colore effettivo. Per supportare questo modello, alcuni valori e oggetti sono stati definiti nel dizionario risorse della pagina:
<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>
Si noti l'uso di OnPlatform
per definire le dimensioni di un BoxView
oggetto e l'altezza delle ListView
righe. Anche se i valori per tutte le piattaforme sono gli stessi, il markup potrebbe essere facilmente adattato per altri valori per ottimizzare lo schermo.
Convertitori di valori per i binding
Il file XAML demo listView precedente visualizza le singole R
proprietà , G
e B
della Xamarin.FormsColor
struttura. Queste proprietà sono di tipo double
e vanno da 0 a 1. Se si desidera visualizzare i valori esadecimali, non è possibile usare StringFormat
semplicemente con una specifica di formattazione "X2". Questo funziona solo per i numeri interi e oltre, i double
valori devono essere moltiplicati per 255.
Questo piccolo problema è stato risolto con un convertitore di valori, detto anche convertitore di associazioni. Si tratta di una classe che implementa l'interfaccia IValueConverter
, ovvero ha due metodi denominati Convert
e ConvertBack
. Il metodo viene chiamato quando un valore viene trasferito dall'origine alla destinazione. Il Convert
ConvertBack
metodo viene chiamato per i trasferimenti dalla destinazione all'origine in OneWayToSource
o TwoWay
associazioni:
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;
}
}
}
Il ConvertBack
metodo non svolge un ruolo in questo programma perché le associazioni sono solo un modo dall'origine alla destinazione.
Un'associazione fa riferimento a un convertitore di associazioni con la Converter
proprietà . Un convertitore di associazioni può anche accettare un parametro specificato con la ConverterParameter
proprietà . Per una certa versatilità, questo è il modo in cui viene specificato il moltiplicatore. Il convertitore di associazioni controlla il parametro del convertitore per un valore valido double
.
Il convertitore viene creato un'istanza nel dizionario risorse in modo che possa essere condiviso tra più associazioni:
<local:DoubleToIntConverter x:Key="intConverter" />
Tre data binding fanno riferimento a questa singola istanza. Si noti che l'estensione di markup contiene un'estensione Binding
di markup incorporata StaticResource
:
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
Ecco il risultato:
è ListView
piuttosto sofisticato nella gestione delle modifiche che potrebbero verificarsi in modo dinamico nei dati sottostanti, ma solo se si esecedono determinati passaggi. Se la raccolta di elementi assegnati alla ItemsSource
proprietà delle modifiche durante il ListView
runtime, ovvero se gli elementi possono essere aggiunti o rimossi dalla raccolta, usare una ObservableCollection
classe per questi elementi. ObservableCollection
implementa l'interfaccia INotifyCollectionChanged
e ListView
installerà un gestore per l'evento CollectionChanged
.
Se le proprietà degli elementi stessi cambiano durante il runtime, gli elementi nella raccolta devono implementare l'interfaccia INotifyPropertyChanged
e segnalare le modifiche ai valori delle proprietà usando l'evento PropertyChanged
. Questo è dimostrato nella parte successiva di questa serie, parte 5. Da Data Binding a MVVM.
Riepilogo
I data binding offrono un potente meccanismo per collegare le proprietà tra due oggetti all'interno di una pagina o tra oggetti visivi e dati sottostanti. Tuttavia, quando l'applicazione inizia a lavorare con le origini dati, un modello architetturale dell'applicazione diffuso inizia a emergere come un paradigma utile. Questo argomento è trattato nella parte 5. Da data binding a MVVM.