Xamarin.Forms Mode liaison

Télécharger l’exemple Télécharger l’exemple

Dans l’article précédent, les pages Alternative Code Binding (Liaison de code alternative) et Alternative XAML Binding (Liaison XAML alternative) présentaient un objet Label avec sa propriété Scale liée à la propriété Value d’un élément Slider. Comme la valeur initiale Slider est 0, la propriété Scale du Label était définie sur 0 au lieu de 1 et le Label disparaissait.

Dans l’exemple DataBindingDemos, la page Reverse Binding (Liaison inverse) est similaire aux programmes de l’article précédent, si ce n’est que la liaison de données est définie sur le Slider plutôt que sur le Label:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.ReverseBindingPage"
             Title="Reverse Binding">
    <StackLayout Padding="10, 0">

        <Label x:Name="label"
               Text="TEXT"
               FontSize="80"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                VerticalOptions="CenterAndExpand"
                Value="{Binding Source={x:Reference label},
                                Path=Opacity}" />
    </StackLayout>
</ContentPage>

Dans un premier temps, cela peut sembler inversé : désormais, le Label est la source de la liaison de données et le Slider en est la cible. La liaison référence la propriété Opacity du Label, qui a une valeur par défaut de 1.

Comme vous pouvez l’imaginer, le Slider est initialisé à la valeur 1 à partir de la valeur Opacity initiale du Label. Ceci est illustré dans la capture d’écran iOS de gauche :

Liaison inverse

Mais vous serez peut-être surpris que le Slider continue de fonctionner, comme le montre la capture d’écran Android. Cela semble suggérer que la liaison de données fonctionne mieux lorsque le Slider est la cible de la liaison à la place du Label, car l’initialisation fonctionne comme on pouvait s’y attendre.

La différence entre l’exemple Reverse Binding (Liaison inverse) et les exemples précédents concerne le mode de liaison.

Mode de liaison par défaut

Le mode de liaison est spécifié avec un membre de l’énumération BindingMode :

  • Default
  • TwoWay – les données vont dans les deux sens entre la source et la cible
  • OneWay – les données vont de la source à la cible
  • OneWayToSource – les données vont de la cible à la source
  • OneTime – les données passent de la source à la cible, mais uniquement lorsque les BindingContext modifications (nouveauté avec Xamarin.Forms la version 3.0)

Chaque propriété pouvant être liée a un mode de liaison par défaut qui est défini lorsque la propriété pouvant être liée est créée, et qui est disponible à partir de la propriété DefaultBindingMode de l’objet BindableProperty. Ce mode de liaison par défaut indique le mode en vigueur lorsque cette propriété est une cible de liaison de données.

Le mode de liaison par défaut pour la plupart des propriétés, telles que Rotation, Scale et Opacity, est OneWay. Lorsque ces propriétés sont des cibles de liaison de données, la propriété cible est définie à partir de la source.

Toutefois, le mode de liaison par défaut pour la propriété Value du Slider est TwoWay. Cela signifie que lorsque la propriété Value est une cible de liaison de données, la cible est définie à partir de la source (comme d’habitude) mais la source est également définie à partir de la cible. C’est ce qui permet de définir le Slider à partir de la valeur Opacity initiale.

Cette liaison bidirectionnelle peut sembler créer une boucle infinie, mais ce n’est pas le cas. Les propriétés pouvant être liées ne signalent pas une modification de propriété, sauf si la propriété change véritablement. Cela empêche une boucle infinie.

Liaisons bidirectionnelles

La plupart des propriétés pouvant être liées ont le mode de liaison par défaut OneWay, mais les propriétés suivantes ont le mode de liaison par défaut TwoWay :

  • Propriété Date de DatePicker
  • Propriété Text de Editor, Entry, SearchBar et EntryCell
  • Propriété IsRefreshing de ListView
  • Propriété SelectedItem de MultiPage
  • Propriétés SelectedIndex et SelectedItem de Picker
  • Propriété Value de Slider et Stepper
  • Propriété IsToggled de Switch
  • Propriété On de SwitchCell
  • Propriété Time de TimePicker

Ces propriétés particulières sont définies comme TwoWay pour une très bonne raison :

Lorsque des liaisons de données sont utilisées avec l’architecture d’application MVVM (modèle-vue-vue modèle), la classe ViewModel est la source de la liaison de données et la vue, qui se compose de vues telles que Slider, est la cible de la liaison de données. Les liaisons MVVM ressemblent plus à l’exemple Reverse Binding (Liaison inverse) que les liaisons des exemples précédents. Il est très probable que vous souhaitiez initialiser chaque vue de la page avec la valeur de la propriété correspondante dans le ViewModel, mais des modifications dans la vue doivent également affecter la propriété ViewModel.

Les propriétés dotées du mode de liaison par défaut TwoWay sont les plus susceptibles d’être utilisées dans les scénarios MVVM.

Liaisons unidirectionnelles vers la source

Les propriétés pouvant être liées en lecture seule disposent du mode de liaison par défaut OneWayToSource. Il existe une seule propriété pouvant être liée en lecture/écriture qui dispose du mode de liaison par défaut OneWayToSource :

  • Propriété SelectedItem de ListView

Le raisonnement est qu’une liaison sur la propriété SelectedItem doit aboutir à la définition de la source de liaison. Un exemple qui sera présenté ultérieurement dans cet article remplace ce comportement.

Liaisons à usage unique

Plusieurs propriétés ont le mode de liaison par défaut OneTime, y compris la propriété IsTextPredictionEnabled de Entry.

Les propriétés cibles disposant du mode de liaison OneTime sont mises à jour uniquement lorsque le contexte de liaison change. Pour les liaisons sur ces propriétés cibles, cela simplifie l’infrastructure de liaison, car il n’est pas nécessaire de surveiller les modifications des propriétés sources.

Objets ViewModel et notifications de modification de propriété

La page Simple Color Selector (Sélecteur de couleur simple) illustre l’utilisation d’un ViewModel simple. Les liaisons de données permettent à l’utilisateur de sélectionner une couleur à l’aide de trois éléments Slider pour la teinte, la saturation et la luminosité.

Le ViewModel est la source de la liaison de données. Le ViewModel ne définit pas de propriétés pouvant être liées, mais il implémente un mécanisme de notification qui permet d’avertir l’infrastructure de liaison lorsque la valeur d’une propriété change. Ce mécanisme de notification est l’interface INotifyPropertyChanged qui définit un événement unique nommé PropertyChanged. Une classe qui implémente cette interface déclenche généralement l’événement lorsque l’une de ses propriétés publiques change de valeur. L’événement n’a pas besoin d’être déclenché si la propriété ne change jamais. (L’interface INotifyPropertyChanged est également implémentée par BindableObject et un événement PropertyChanged est déclenché chaque fois qu’une propriété pouvant être liée change de valeur.)

La classe HslColorViewModel définit cinq propriétés : les propriétés Hue, Saturation, Luminosity et Color sont liées entre elles. Lorsque l’un des trois composants de couleur change de valeur, la propriété Color est recalculée et les événements PropertyChanged sont déclenchés pour les quatre propriétés :

public class HslColorViewModel : INotifyPropertyChanged
{
    Color color;
    string name;

    public event PropertyChangedEventHandler PropertyChanged;

    public double Hue
    {
        set
        {
            if (color.Hue != value)
            {
                Color = Color.FromHsla(value, color.Saturation, color.Luminosity);
            }
        }
        get
        {
            return color.Hue;
        }
    }

    public double Saturation
    {
        set
        {
            if (color.Saturation != value)
            {
                Color = Color.FromHsla(color.Hue, value, color.Luminosity);
            }
        }
        get
        {
            return color.Saturation;
        }
    }

    public double Luminosity
    {
        set
        {
            if (color.Luminosity != value)
            {
                Color = Color.FromHsla(color.Hue, color.Saturation, value);
            }
        }
        get
        {
            return color.Luminosity;
        }
    }

    public Color Color
    {
        set
        {
            if (color != value)
            {
                color = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hue"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Saturation"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Luminosity"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

                Name = NamedColor.GetNearestColorName(color);
            }
        }
        get
        {
            return color;
        }
    }

    public string Name
    {
        private set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
            }
        }
        get
        {
            return name;
        }
    }
}

Lorsque la propriété Color change, la méthode statique GetNearestColorName dans la classe NamedColor (également incluse dans la solution DataBindingDemos) obtient la couleur nommée la plus proche et définit la propriété Name. Cette propriété Name a un accesseur set privé et ne peut donc pas être définie depuis l’extérieur de la classe.

Lorsqu’un ViewModel est défini comme source de liaison, l’infrastructure de liaison attache un gestionnaire à l’événement PropertyChanged. De cette façon, la liaison peut être avertie des modifications apportées aux propriétés et peut alors définir les propriétés cibles à partir des valeurs modifiées.

Toutefois, lorsqu’une propriété cible (ou la définition Binding sur une propriété cible) a le BindingModeOneTime, l’infrastructure de liaison n’est pas tenue d’attacher un gestionnaire sur l’événement PropertyChanged. La propriété cible est mise à jour uniquement lorsque le BindingContext change et non pas lorsque la propriété source proprement dite change.

Le fichier XAML Simple Color Selector instancie le HslColorViewModel dans le dictionnaire de ressources de la page et initialise la propriété Color. La propriété BindingContext de l’objet Grid est définie sur une extension de liaison StaticResource pour référencer cette ressource :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SimpleColorSelectorPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:HslColorViewModel x:Key="viewModel"
                                     Color="MediumTurquoise" />

            <Style TargetType="Slider">
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid BindingContext="{StaticResource viewModel}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <BoxView Color="{Binding Color}"
                 Grid.Row="0" />

        <StackLayout Grid.Row="1"
                     Margin="10, 0">

            <Label Text="{Binding Name}"
                   HorizontalTextAlignment="Center" />

            <Slider Value="{Binding Hue}" />

            <Slider Value="{Binding Saturation}" />

            <Slider Value="{Binding Luminosity}" />
        </StackLayout>
    </Grid>
</ContentPage>

BoxView, Label et les trois vues Slider héritent du contexte de liaison de l’objet Grid. Ces vues sont toutes les cibles de liaison qui référencent les propriétés de la source dans le ViewModel. Pour la propriété Color de BoxView et la propriété Text de Label, les liaisons de données sont OneWay : les propriétés dans la vue sont définies à partir des propriétés dans le ViewModel.

La propriété Value de Slider, toutefois, est TwoWay. Cela permet de définir chaque élément Slider à partir du ViewModel, ainsi que le ViewModel à partir de chaque élément Slider.

À la première exécution du programme, BoxView, Label et les trois éléments Slider sont tous définis à partir du ViewModel basé sur la propriété Color initiale, définie lorsque le ViewModel a été instancié. Ceci est illustré dans la capture d’écran iOS de gauche :

couleurs simple Sélecteur de couleurs simple Sélecteur

Lorsque vous manipulez les curseurs, les BoxView et Label sont mis à jour en conséquence, comme illustré par la capture d’écran Android.

L’instanciation du ViewModel dans le dictionnaire de ressources est une approche courante. Il est également possible d’instancier le ViewModel dans les balises d’élément de propriété pour la propriété BindingContext. Dans le fichier XAML Simple Color Selector, essayez de supprimer le HslColorViewModel du dictionnaire de ressources et définissez-le sur la propriété BindingContext de l’objet Grid, comme suit :

<Grid>
    <Grid.BindingContext>
        <local:HslColorViewModel Color="MediumTurquoise" />
    </Grid.BindingContext>

    ···

</Grid>

Le contexte de liaison peut être défini de diverses façons. Parfois, le fichier code-behind instancie le ViewModel et le définit sur la propriété BindingContext de la page. Toutes ces approches sont valides.

Remplacement du mode de liaison

Si le mode de liaison par défaut sur la propriété cible ne convient pas pour une liaison de données particulière, il est possible de le remplacer en définissant la propriété Mode de Binding (ou la propriété Mode de l’extension de balisage Binding) sur l’un des membres de l’énumération BindingMode.

Toutefois, la définition de la propriété Mode sur TwoWay ne fonctionne pas toujours comme prévu. Par exemple, essayez de modifier le fichier XAML Alternative XAML Binding pour inclure TwoWay dans la définition de la liaison :

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       Scale="{Binding Source={x:Reference slider},
                       Path=Value,
                       Mode=TwoWay}" />

On pourrait s’attendre à ce que l’élément Slider soit initialisé sur la valeur initiale de la propriété Scale, à savoir 1, mais ce n’est pas le cas. Lorsqu’une liaison TwoWay est initialisée, la cible est tout d’abord définie à partir de la source, ce qui signifie que la propriété Scale est définie sur la valeur par défaut 0 de Slider. Lorsque la liaison TwoWay est définie sur l’élément Slider, l’élément Slider est initialement défini à partir de la source.

Vous pouvez définir le mode de liaison sur OneWayToSource dans l’exemple Alternative XAML Binding (Liaison XAML alternative) :

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       Scale="{Binding Source={x:Reference slider},
                       Path=Value,
                       Mode=OneWayToSource}" />

Maintenant, l’élément Slider est initialisé à 1 (valeur par défaut de Scale) mais la manipulation de l’élément Slider n’affecte pas la propriété Scale ; ce n’est donc pas très utile.

Notes

La classe VisualElement définit également les propriétés ScaleX et ScaleY, qui peuvent redimensionner l’élément VisualElement dans les directions horizontale et verticale.

Une application très utile du remplacement du mode de liaison par défaut par TwoWay concerne la propriété SelectedItem de ListView. Le mode de liaison par défaut est OneWayToSource. Quand une liaison de données est définie sur la propriété SelectedItem pour référencer une propriété source dans un ViewModel, cette propriété source est définie à partir de la sélection ListView. Toutefois, dans certains cas, vous voudrez peut-être également initialiser ListView à partir du ViewModel.

La page Sample Settings (Exemples de paramètres) illustre cette technique. Cette page représente une implémentation simple des paramètres d’application, qui sont très souvent définis dans un ViewModel, tel que ce fichier SampleSettingsViewModel :

public class SampleSettingsViewModel : INotifyPropertyChanged
{
    string name;
    DateTime birthDate;
    bool codesInCSharp;
    double numberOfCopies;
    NamedColor backgroundNamedColor;

    public event PropertyChangedEventHandler PropertyChanged;

    public SampleSettingsViewModel(IDictionary<string, object> dictionary)
    {
        Name = GetDictionaryEntry<string>(dictionary, "Name");
        BirthDate = GetDictionaryEntry(dictionary, "BirthDate", new DateTime(1980, 1, 1));
        CodesInCSharp = GetDictionaryEntry<bool>(dictionary, "CodesInCSharp");
        NumberOfCopies = GetDictionaryEntry(dictionary, "NumberOfCopies", 1.0);
        BackgroundNamedColor = NamedColor.Find(GetDictionaryEntry(dictionary, "BackgroundNamedColor", "White"));
    }

    public string Name
    {
        set { SetProperty(ref name, value); }
        get { return name; }
    }

    public DateTime BirthDate
    {
        set { SetProperty(ref birthDate, value); }
        get { return birthDate; }
    }

    public bool CodesInCSharp
    {
        set { SetProperty(ref codesInCSharp, value); }
        get { return codesInCSharp; }
    }

    public double NumberOfCopies
    {
        set { SetProperty(ref numberOfCopies, value); }
        get { return numberOfCopies; }
    }

    public NamedColor BackgroundNamedColor
    {
        set
        {
            if (SetProperty(ref backgroundNamedColor, value))
            {
                OnPropertyChanged("BackgroundColor");
            }
        }
        get { return backgroundNamedColor; }
    }

    public Color BackgroundColor
    {
        get { return BackgroundNamedColor?.Color ?? Color.White; }
    }

    public void SaveState(IDictionary<string, object> dictionary)
    {
        dictionary["Name"] = Name;
        dictionary["BirthDate"] = BirthDate;
        dictionary["CodesInCSharp"] = CodesInCSharp;
        dictionary["NumberOfCopies"] = NumberOfCopies;
        dictionary["BackgroundNamedColor"] = BackgroundNamedColor.Name;
    }

    T GetDictionaryEntry<T>(IDictionary<string, object> dictionary, string key, T defaultValue = default(T))
    {
        return dictionary.ContainsKey(key) ? (T)dictionary[key] : defaultValue;
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Chaque paramètre d’application est une propriété enregistrée dans le Xamarin.Forms dictionnaire de propriétés dans une méthode nommée SaveState et chargée à partir de ce dictionnaire dans le constructeur. Vers le bas de cette classe, deux méthodes aident à rationaliser les objets ViewModel et à les rendre moins sujets aux erreurs. La méthode OnPropertyChanged du bas possède un paramètre facultatif qui est défini sur la propriété appelante. Il permet d’éviter les fautes d’orthographe lors de la spécification du nom de la propriété sous forme de chaîne.

La méthode SetProperty dans la classe fait encore plus : elle compare la valeur définie sur la propriété avec la valeur stockée en tant que champ, et elle appelle OnPropertyChanged seulement si ces deux valeurs ne sont pas identiques.

La classe SampleSettingsViewModel définit deux propriétés pour la couleur d’arrière-plan : la propriété BackgroundNamedColor est de type NamedColor, qui est une classe également incluse dans la solution DataBindingDemos. La propriété BackgroundColor est de type Color et est obtenue à partir de la propriété Color de l’objet NamedColor.

La NamedColor classe utilise la réflexion .NET pour énumérer tous les champs publics statiques de la Xamarin.FormsColor structure et les stocker avec leurs noms dans une collection accessible à partir de la propriété statique All :

public class NamedColor : IEquatable<NamedColor>, IComparable<NamedColor>
{
    // Instance members
    private NamedColor()
    {
    }

    public string Name { private set; get; }

    public string FriendlyName { private set; get; }

    public Color Color { private set; get; }

    public string RgbDisplay { private set; get; }

    public bool Equals(NamedColor other)
    {
        return Name.Equals(other.Name);
    }

    public int CompareTo(NamedColor other)
    {
        return Name.CompareTo(other.Name);
    }

    // Static members
    static NamedColor()
    {
        List<NamedColor> all = new List<NamedColor>();
        StringBuilder stringBuilder = new StringBuilder();

        // Loop through the public static fields of the Color structure.
        foreach (FieldInfo fieldInfo in typeof(Color).GetRuntimeFields())
        {
            if (fieldInfo.IsPublic &&
                fieldInfo.IsStatic &&
                fieldInfo.FieldType == typeof(Color))
            {
                // Convert the name to a friendly name.
                string name = fieldInfo.Name;
                stringBuilder.Clear();
                int index = 0;

                foreach (char ch in name)
                {
                    if (index != 0 && Char.IsUpper(ch))
                    {
                        stringBuilder.Append(' ');
                    }
                    stringBuilder.Append(ch);
                    index++;
                }

                // Instantiate a NamedColor object.
                Color color = (Color)fieldInfo.GetValue(null);

                NamedColor namedColor = new NamedColor
                {
                    Name = name,
                    FriendlyName = stringBuilder.ToString(),
                    Color = color,
                    RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
                                                (int)(255 * color.R),
                                                (int)(255 * color.G),
                                                (int)(255 * color.B))
                };

                // Add it to the collection.
                all.Add(namedColor);
            }
        }
        all.TrimExcess();
        all.Sort();
        All = all;
    }

    public static IList<NamedColor> All { private set; get; }

    public static NamedColor Find(string name)
    {
        return ((List<NamedColor>)All).Find(nc => nc.Name == name);
    }

    public static string GetNearestColorName(Color color)
    {
        double shortestDistance = 1000;
        NamedColor closestColor = null;

        foreach (NamedColor namedColor in NamedColor.All)
        {
            double distance = Math.Sqrt(Math.Pow(color.R - namedColor.Color.R, 2) +
                                        Math.Pow(color.G - namedColor.Color.G, 2) +
                                        Math.Pow(color.B - namedColor.Color.B, 2));

            if (distance < shortestDistance)
            {
                shortestDistance = distance;
                closestColor = namedColor;
            }
        }
        return closestColor.Name;
    }
}

La classe App dans le projet DataBindingDemos définit une propriété nommée Settings de type SampleSettingsViewModel. Cette propriété est initialisée lorsque la classe App est instanciée, et la méthode SaveState est appelée lorsque la méthode OnSleep est appelée :

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        Settings = new SampleSettingsViewModel(Current.Properties);

        MainPage = new NavigationPage(new MainPage());
    }

    public SampleSettingsViewModel Settings { private set; get; }

    protected override void OnStart()
    {
        // Handle when your app starts
    }

    protected override void OnSleep()
    {
        // Handle when your app sleeps
        Settings.SaveState(Current.Properties);
    }

    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}

Pour plus d’informations sur les méthodes de cycle de vie d’application, consultez l’article Cycle de vie d’application.

Presque tout le reste est géré dans le fichier SampleSettingsPage.xaml. Le BindingContext de la page est défini à l’aide d’une extension de balisage Binding : la source de la liaison est la propriété statique Application.Current, qui est l’instance de la classe App dans le projet et le Path est défini sur la propriété Settings, qui est l’objet SampleSettingsViewModel :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SampleSettingsPage"
             Title="Sample Settings"
             BindingContext="{Binding Source={x:Static Application.Current},
                                      Path=Settings}">

    <StackLayout BackgroundColor="{Binding BackgroundColor}"
                 Padding="10"
                 Spacing="10">

        <StackLayout Orientation="Horizontal">
            <Label Text="Name: "
                   VerticalOptions="Center" />

            <Entry Text="{Binding Name}"
                   Placeholder="your name"
                   HorizontalOptions="FillAndExpand"
                   VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Birth Date: "
                   VerticalOptions="Center" />

            <DatePicker Date="{Binding BirthDate}"
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Do you code in C#? "
                   VerticalOptions="Center" />

            <Switch IsToggled="{Binding CodesInCSharp}"
                    VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Number of Copies: "
                   VerticalOptions="Center" />

            <Stepper Value="{Binding NumberOfCopies}"
                     VerticalOptions="Center" />

            <Label Text="{Binding NumberOfCopies}"
                   VerticalOptions="Center" />
        </StackLayout>

        <Label Text="Background Color:" />

        <ListView x:Name="colorListView"
                  ItemsSource="{x:Static local:NamedColor.All}"
                  SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"
                  VerticalOptions="FillAndExpand"
                  RowHeight="40">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <BoxView Color="{Binding Color}"
                                     HeightRequest="32"
                                     WidthRequest="32"
                                     VerticalOptions="Center" />

                            <Label Text="{Binding FriendlyName}"
                                   FontSize="24"
                                   VerticalOptions="Center" />
                        </StackLayout>                        
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

Tous les enfants de la page héritent du contexte de liaison. La plupart des autres liaisons de cette page se rapportent aux propriétés dans SampleSettingsViewModel. La propriété BackgroundColor est utilisée pour définir la propriété BackgroundColor de StackLayout et les propriétés Entry, DatePicker, Switch et Stepper sont toutes liées à d’autres propriétés dans le ViewModel.

La propriété ItemsSource de ListView est définie sur la propriété statique NamedColor.All. Cela remplit ListView avec toutes les instances NamedColor. Pour chaque élément figurant dans ListView, le contexte de liaison de l’élément est défini sur un objet NamedColor. Les objets BoxView et Label dans l’objet ViewCell sont liés aux propriétés figurant dans NamedColor.

La propriété SelectedItem de ListView est de type NamedColor et est liée à la propriété BackgroundNamedColor de SampleSettingsViewModel :

SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"

Le mode de liaison par défaut pour SelectedItem est OneWayToSource, qui définit la propriété ViewModel à partir de l’élément sélectionné. Le mode TwoWay permet d’initialiser SelectedItem à partir du ViewModel.

Toutefois, lorsque SelectedItem est défini de cette façon, ListView ne défile pas automatiquement pour afficher l’élément sélectionné. Un peu de code est nécessaire dans le fichier code-behind :

public partial class SampleSettingsPage : ContentPage
{
    public SampleSettingsPage()
    {
        InitializeComponent();

        if (colorListView.SelectedItem != null)
        {
            colorListView.ScrollTo(colorListView.SelectedItem,
                                   ScrollToPosition.MakeVisible,
                                   false);
        }
    }
}

La capture d’écran iOS de gauche montre le programme lors de sa première exécution. Le constructeur dans SampleSettingsViewModel initialise la couleur d’arrière-plan sur blanc, et c’est ce qui est sélectionné dans ListView :

Exemples de paramètres

L’autre capture d’écran montre les paramètres modifiés. Lorsque vous testez cette page, veillez à mettre en veille le programme ou à l’arrêter sur l’appareil ou l’émulateur où il s’exécute. L’arrêt du programme à partir du débogueur Visual Studio n’entraîne pas le remplacement OnSleep dans la classe App à appeler.

Dans le prochain article, vous verrez comment spécifier un formatage de chaîne des liaisons de données qui sont définies sur la propriété Text de Label.