Condividi tramite


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à TextPropertyassociabile .

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 ContentPropertyBinding è 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:

Associazioni da visualizzazione a visualizzazione

Modalità di associazione

Una singola visualizzazione può avere data binding su diverse delle relative proprietà. Tuttavia, ogni vista può avere un BindingContextsolo 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 destinazione
  • OneWayToSource — i valori vengono trasferiti dalla destinazione all'origine
  • TwoWay — i valori vengono trasferiti in entrambi i modi tra origine e destinazione
  • OneTime— i dati passano dall'origine alla destinazione, ma solo quando cambiano BindingContext

Il programma seguente illustra un uso comune delle modalità di OneWayToSource associazione e TwoWay . Quattro Slider viste sono destinate a controllare le Scaleproprietà , RotateRotateX, 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 Labele 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 Scaleproprietà , RotateRotateX, 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 BindingContextoggetto , ovvero l'oggetto Label denominato label. Queste tre Slider visualizzazioni causano modifiche alle Rotateproprietà , RotateXe 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.

Binding indietro

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

ListView definisce una ItemsSource proprietà di tipo IEnumerablee 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 ListViewe 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 stringe 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:

Associazione a una raccolta

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

Associazione a una raccolta con un oggetto DataTemplate

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 Rproprietà , Ge 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 ConvertConvertBack 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:

Associazione a una raccolta con datatemplate e convertitori

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