Xamarin.Forms Associazioni di base
Un Xamarin.Forms data binding collega una coppia di proprietà tra due oggetti, almeno uno dei quali è in genere un oggetto interfaccia utente. Questi due oggetti vengono chiamati destinazione e origine:
- La destinazione è l'oggetto (e la proprietà) su cui è impostato il data binding.
- L'origine è l'oggetto (e la proprietà) a cui fa riferimento il data binding.
In certi casi questa distinzione può generare confusione. Nel caso più semplice, i dati passano dall'origine alla destinazione. Ciò significa che il valore della proprietà di destinazione è impostato dal valore della proprietà di origine. Tuttavia in alcuni casi i dati possono passare dalla destinazione all'origine o in entrambe le direzioni. Per evitare confusione, tenere presente che la destinazione è sempre l'oggetto sul quale viene impostato il data binding, anche se trasmette dati anziché riceverli.
Binding con un contesto di binding
In genere i data binding sono specificati interamente in XAML, ma è istruttivo vederli anche sotto forma di codice. La pagina Basic Code Binding (Binding di codice di base) contiene un file XAML con un elemento Label
e un elemento Slider
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicCodeBindingPage"
Title="Basic Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="48"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
L'elemento Slider
è impostato per un intervallo compreso tra 0 e 360. La finalità di questo programma è la rotazione di Label
tramite l'azionamento di Slider
.
Senza i data binding sarebbe necessario impostare l'evento ValueChanged
di Slider
su un gestore eventi che accede alla proprietà Value
di Slider
e imposta tale valore sulla proprietà Rotation
di Label
. Il data binding consente di automatizzare questo processo: il gestore eventi e il codice al suo interno non sono più necessari.
È possibile impostare un binding su un'istanza di qualsiasi classe derivata da BindableObject
, che include i derivati Element
, VisualElement
, View
e View
. Il binding è sempre impostato sull'oggetto di destinazione. Il binding fa riferimento all'oggetto di origine. Per impostare il data binding usare i due membri seguenti della classe di destinazione:
- La proprietà
BindingContext
specifica l'oggetto di origine. - Il metodo
SetBinding
specifica la proprietà di destinazione e la proprietà di origine.
In questo esempio, Label
è la destinazione del binding e Slider
è l'origine del binding. Le modifiche nell'origine Slider
hanno effetto sulla rotazione della destinazione Label
. I dati passano dall'origine alla destinazione.
Il metodo SetBinding
definito da BindableObject
ha un argomento di tipo BindingBase
da cui deriva la classe Binding
, ma sono presenti altri metodi SetBinding
definiti dalla classe BindableObjectExtensions
. Il file code-behind nell'esempio Basic Code Binding (Binding di codice di base) usa un metodo di estensione SetBinding
più semplice di questa classe.
public partial class BasicCodeBindingPage : ContentPage
{
public BasicCodeBindingPage()
{
InitializeComponent();
label.BindingContext = slider;
label.SetBinding(Label.RotationProperty, "Value");
}
}
L'oggetto Label
è la destinazione del binding, ovvero l'oggetto sul quale è impostata questa proprietà e sul quale viene chiamato il metodo. La proprietà BindingContext
indica l'origine del binding, ovvero Slider
.
Il metodo SetBinding
viene chiamato sulla destinazione del binding, ma specifica sia la proprietà di destinazione che la proprietà di origine. La proprietà di destinazione viene specificata come oggetto BindableProperty
: Label.RotationProperty
. La proprietà di origine viene specificata come stringa e indica la proprietà Value
di Slider
.
Il metodo SetBinding
rivela una delle regole più importanti per i data binding:
La proprietà di destinazione deve essere supportata da una proprietà con binding.
Questa regola implica che l'oggetto di destinazione deve essere un'istanza di una classe che deriva da BindableObject
. Vedere l'articolo Bindable Properties (Proprietà con binding) per una panoramica degli oggetti e delle proprietà associabili.
Non è presente una regola di questo tipo per la proprietà di origine, che viene specificata sotto forma di stringa. A livello interno, per l'accesso alla proprietà viene usata una reflection. In questo caso specifico, tuttavia, la proprietà Value
è anche supportata da una proprietà con binding.
Il codice può essere semplificato leggermente: la proprietà con binding RotationProperty
è definita da VisualElement
e anche ereditata da Label
e ContentPage
, pertanto il nome della classe non è obbligatorio nella chiamata SetBinding
:
label.SetBinding(RotationProperty, "Value");
Tuttavia l'inclusione del nome della classe è un buon promemoria dell'oggetto di destinazione.
Quando si aziona l'elemento Slider
, l'elemento Label
ruota di conseguenza:
La pagina Basic Xaml Binding (Binding XAML di base) è identica alla pagina Basic Code Binding (Binding di codice di base) salvo per il fatto che definisce l'intero data binding in XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicXamlBindingPage"
Title="Basic XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Come nel codice, il data binding è impostato sull'oggetto di destinazione, ovvero su Label
. Sono presenti due estensioni di markup XAML. Le estensioni sono immediatamente riconoscibili per i delimitatori parentesi graffa:
- L'estensione di markup
x:Reference
è obbligatoria per il riferimento all'oggetto di origine, ovvero l'elementoSlider
con nomeslider
. - L'estensione di markup
Binding
collega la proprietàRotation
diLabel
alla proprietàValue
diSlider
.
Per altre informazioni sulle estensioni di markup XAML, vedere l'articolo Estensioni di markup XAML. L'estensione di markup x:Reference
è supportata dalla classe ReferenceExtension
; Binding
è supportata dalla classe BindingExtension
. Come indicano i prefissi dello spazio dei nomi XML, x:Reference
fa parte della specifica XAML 2009, mentre Binding
fa parte di Xamarin.Forms. Si noti che all'interno delle parentesi graffe non appaiono virgolette.
È facile dimenticare l'estensione di markup x:Reference
durante l'impostazione di BindingContext
. Inoltre spesso si imposta erroneamente la proprietà sul nome dell'origine di binding, come visualizzato di seguito:
BindingContext="slider"
Questa prassi non è corretta. Questo markup imposta la proprietà BindingContext
su un oggetto string
i cui caratteri visualizzano "slider".
Si noti che la proprietà di origine è specificata con la proprietà Path
di BindingExtension
, che corrisponde alla proprietà Path
della classe Binding
.
Il markup visualizzato nella pagina Basic XAML Binding (Binding XAML di base) può essere semplificato: le estensioni di markup XAML come x:Reference
e Binding
possono avere attributi content property definiti. Per le estensioni di markup XAML ciò significa che non è necessario che sia visualizzato il nome della proprietà. La proprietà Name
è la proprietà di contenuto di x:Reference
, mentre la proprietà Path
è la proprietà di contenuto di Binding
. Questo significa che entrambe possono essere eliminate delle espressioni:
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference slider}"
Rotation="{Binding Value}" />
Binding senza un contesto di binding
La proprietà BindingContext
è un componente importante per i data binding, ma non è sempre necessaria. In alternativa l'oggetto di origine può essere specificato nella chiamata SetBinding
o nell'estensione di markup Binding
.
Questo è dimostrato nell'esempio Alternative Code Binding (Binding di codice alternativo). Il file XAML è simile a quello dell'esempio Basic Code Binding (Binding di codice di base), con la differenza che Slider
viene definito per il controllo della proprietà Scale
di Label
. Per questo motivo, Slider
è impostato per un intervallo compreso tra -2 e 2:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeCodeBindingPage"
Title="Alternative Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Il file code-behind imposta il binding con il metodo SetBinding
definito da BindableObject
. L'argomento è un costruttore per la classe Binding
:
public partial class AlternativeCodeBindingPage : ContentPage
{
public AlternativeCodeBindingPage()
{
InitializeComponent();
label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider));
}
}
Il costruttore Binding
ha 6 parametri, pertanto il parametro source
è specificato con un argomento denominato. L'argomento è l'oggetto slider
.
L'esecuzione del programma può dare risultati inattesi:
La schermata iOS a sinistra visualizza l'aspetto della schermata quando la pagina viene visualizzata per la prima volta. Dove si trova Label
?
Il problema è che Slider
ha un valore iniziale pari a 0. Di conseguenza anche la proprietà Scale
di Label
è impostata su 0, e questo sovrascrive il valore predefinito 1. Di conseguenza, inizialmente Label
non è visibile. Come dimostra lo screenshot di Android, è possibile modificare Slider
in modo da rendere di nuovo visibile Label
, ma la scomparsa iniziale dell'elemento può risultare inattesa.
L'articolo successivo illustra come evitare questo problema inizializzando Slider
in base al valore predefinito della proprietà Scale
.
Nota
La classe VisualElement
definisce anche le proprietà ScaleX
e ScaleY
, che possono modificare in scala VisualElement
in modi diversi in direzione orizzontale e verticale.
La pagina Alternative XAML Binding (Binding XAML alternativo) illustra il binding equivalente interamente in XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeXamlBindingPage"
Title="Alternative XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="{Binding Source={x:Reference slider},
Path=Value}" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
A questo punto l'estensione di markup Binding
ha due proprietà impostate, Source
e Path
, separate da una virgola. Se si preferisce è possibile visualizzarle sulla stessa riga:
Scale="{Binding Source={x:Reference slider}, Path=Value}" />
La proprietà Source
è impostata su un'estensione di markup x:Reference
incorporata, che per il resto ha la stessa sintassi dell'impostazione di BindingContext
. Si noti che tra le parentesi graffe non vengono visualizzate virgolette e che le due proprietà devono essere separate da virgole.
La proprietà del contenuto dell'estensione di markup Binding
è Path
, ma la parte Path=
dell'estensione di markup può essere eliminata solo se è la prima proprietà nell'espressione. Per eliminare la parte Path=
è necessario scambiare le due proprietà:
Scale="{Binding Value, Source={x:Reference slider}}" />
Le estensioni di markup XAML sono in genere delimitate da parentesi graffe, ma possono anche essere espresse come elementi oggetto:
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Source="{x:Reference slider}"
Path="Value" />
</Label.Scale>
</Label>
Ora le proprietà Source
e Path
sono attributi XAML normali: i valori vengono visualizzati all'interno di virgolette e gli attributi non sono separati da una virgola. Anche l'estensione di markup x:Reference
può trasformarsi in un elemento oggetto:
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Path="Value">
<Binding.Source>
<x:Reference Name="slider" />
</Binding.Source>
</Binding>
</Label.Scale>
</Label>
Questa sintassi non è comune, ma può risultare necessaria in presenza di oggetti complessi.
Gli esempi illustrati finora impostano la proprietà BindingContext
e la proprietà Source
di Binding
su un'estensione di markup x:Reference
per fare riferimento a un'altra visualizzazione nella pagina. Queste due proprietà sono di tipo Object
e possono essere impostate su qualsiasi oggetto che include proprietà adatte alle origini di binding.
Negli articoli successivi si apprenderà che è possibile impostare la proprietà BindingContext
o Source
su un'estensione di markup x:Static
per fare riferimento al valore di una proprietà o un campo statico, oppure su un'estensione di markup StaticResource
per fare riferimento a un oggetto archiviato in un dizionario risorse, o ancora direttamente su un oggetto, che è in genere (ma non sempre) un'istanza di un elemento ViewModel.
La proprietà BindingContext
può anche essere impostata su un oggetto Binding
in modo che le proprietà Source
e Path
di Binding
definiscano il contesto di binding.
Ereditarietà del contesto di binding
In questo articolo si è visto che è possibile specificare l'oggetto di origine usando la proprietà BindingContext
o la proprietà Source
dell'oggetto Binding
. Se sono impostate entrambe, la proprietà Source
di Binding
ha la precedenza su BindingContext
.
La proprietà BindingContext
presenta una caratteristica molto importante:
L'impostazione della proprietà BindingContext
viene ereditata nell'intera la struttura ad albero visuale.
Come si vedrà, questo può essere molto utile per semplificare le espressioni di associazione e in alcuni casi, in particolare negli scenari Model-View-ViewModel (MVVM), è essenziale.
L'esempio Binding Context Inheritance (Ereditarietà del contesto di binding) è una dimostrazione semplice dell'ereditarietà del contesto di binding:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BindingContextInheritancePage"
Title="BindingContext Inheritance">
<StackLayout Padding="10">
<StackLayout VerticalOptions="FillAndExpand"
BindingContext="{x:Reference slider}">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="EndAndExpand"
Rotation="{Binding Value}" />
<BoxView Color="#800000FF"
WidthRequest="180"
HeightRequest="40"
HorizontalOptions="Center"
VerticalOptions="StartAndExpand"
Rotation="{Binding Value}" />
</StackLayout>
<Slider x:Name="slider"
Maximum="360" />
</StackLayout>
</ContentPage>
La proprietà BindingContext
di StackLayout
è impostata sull'oggetto slider
. Questo contesto di binding viene ereditato sia da Label
che da BoxView
e le proprietà Rotation
di entrambi sono impostate sulla proprietà Value
di Slider
:
Nell'articolo seguente si vedrà come la modalità di binding può modificare il flusso di dati tra gli oggetti di origine e destinazione.
Collegamenti correlati
Video correlato
Altri video di Xamarin sono disponibili su Channel 9 e YouTube.