• laurent@lioncoding.com

Utiliser des Triggers et Converters pour valider un formulaire dans Xamarin.Forms


Dans cet article , nous verrons comment utiliser des Triggers et Converters de Xamarin.Forms pour valider en temps réel les données saisies sur un formulaire d’authentification.

Appréhender les notions de Converters et de Triggers

  • Les Converters ou convertisseurs

Il arrive des moments où les ViewModels ne peuvent pas adapter les données pour la Vue: C’est là qu’intervient les Convertisseurs.

Ce sont des classes qui implémente l’interface IValueConverter.

Le but pour nous est de convertir des chaînes de caractères en booléen, plus précisément de vérifier si le login et le mot de passe sont syntaxiquement corrects afin de pouvoir renvoyer Vrai ou faux.

  • Les Triggers ou déclencheurs

Comme les Triggers en SQL qui déclenche des actions après vérification d’un certains nombre de conditions définies, les Triggers en Xamarin.Forms fonctionne de la même manière sauf qu’ici il n’y pas de script SQL à écrire; tout se fera dans le XAML(pour la majorité des Triggers ou dans des classes pour certains).

Ils sont déclarés directement dans les balises des objets visuels comme les boutons, les labels, les champs de saisie, …etc.

À la différence des Behaviors(déjà présentés dans ce article) qui réclament des classes spécifiques, les Triggers(pour la plupart) fonctionnent directement sans aucun code à écrire autre que la sa déclaration dans le XAML (la vue).

Il existe différents type de Triggers(Nous consacrerons tout un article sur les Triggers) mais pour cet article, nous ne verrons qu’un seul type appelé MultiTrigger qui donne la possibilité de déclarer plusieurs conditions au lieu d’une pour les autres type de Triggers.

Sujet

Il est question ici de faire une vérification syntaxique des champs de saisie d’une vue présentant un formulaire d’authentification:

  1. Vérifier si l’adresse e-mail respecte l’expression régulière ^[a-z0-9._-]+@[a-z0-9._-]+\\.[a-z]{2,6}$ , sinon rendre visible un message d’erreur et désactiver le bouton d’authentification.
  2. Vérifier également que le mot de passe comporte au moins 8 caractères sinon afficher le message d’erreur et aussi désactiver le bouton d’authentification.
  3. Si rien n’a été saisi dans les champs, ne pas faire apparaître les messages d’erreur.
  4. Activer le bouton de login si tout est correct et au clic, rendre visible un ActivityIndicator.

Création du projet Cross-platform Xamarin

Structure du projet

La structure suivante est celle de notre projet lorsqu’on aura fini de coder. L’objectif en le mettant à ce niveau est de vous montrer les fichiers du projet.

Création des convertisseurs

  • Le Converter qui validera l’adresse e-mail et qui sera utilisé par le MultiTrigger du bouton de login:
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Xamarin.Forms;

namespace FormValidation.Converters
{
    /// <summary>
    /// Converter that check if username is correct and return true, otherwise return false.
    /// </summary>
    public class UsernameCorrectConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return IsUserNameCorrect(value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private bool IsUserNameCorrect(object value)
        {
            if (value is string)
            {
                bool isEmail = Regex.IsMatch(
                    (string)value, "^[a-z0-9._-]+@[a-z0-9._-]+\\.[a-z]{2,6}$");

                int length = ((string)value).Trim().Length;

                if (length >= 7 && length <= 60 && isEmail)
                    return true;
                else
                    return false;

            }
            return false;
        }

    }
}

Vous constatez que notre Converter hérite de la classe IValueConverter et seule la méthode Convert(object value, Type targetType, object parameter, CultureInfo culture) est utilisée. Elle prend en paramètre un objet(value) qui sera notre adresse e-mail, fait les contrôles puis renvoie Vrai ou faux.

  • Un autre convertisseur qui aussi validera l’adresse e-mail mais sera utilisé cette fois-ci par le MultiTrigger du Label en charge d’afficher le message d’erreur pour l’adresse e-mail.
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Xamarin.Forms;

namespace FormValidation.Converters
{
    /// <summary>
    /// Converter that check if username is correct and return true, otherwise return false.
    /// The idea is to be able to show or hide username error label.
    /// </summary>
    public class UsernameCorrectToHideLabelConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return IsUserNameCorrect(value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private bool IsUserNameCorrect(object value)
        {
            if (value == null || ((string)value).Length == 0)
                return true;

            if (value is string)
            {
                bool isEmail = Regex.IsMatch(
                    (string)value, "^[a-z0-9._-]+@[a-z0-9._-]+\\.[a-z]{2,6}$");

                int length = ((string)value).Trim().Length;
                if (length >= 7 && length <= 60 && isEmail)
                    return true;
                else
                    return false;

            }
            return false;
        }
    }
}

Le code de ce convertisseur est pratiquement le même que le précédant mais ici lorsque l’adresse e-mail est vide, la méthode renvoie true. C’est pour répondre au point 3 de notre Sujet qui nous oblige à ne pas afficher les messages d’erreur lorsque rien n’a été saisi dans les champs; mais en même temps le bouton de login sera désactivé(à ne pas perdre de vue)

  • Le Converter qui validera le mot de passe et qui sera utilisé par le MultiTrigger du bouton de login:

On constate que ce convertisseur sera aussi utilisé par le MultiTrigger du bouton du login. Ceci explique pourquoi le préfixe Multi de ce type de Trigger. En effet comme je l’ai mentionné un peu plus haut, il est capable de vérifier plusieurs conditions avant de déclencher l’action qui lui a été assignée.

Le Trigger du bouton passera la valeur du champ de l’e-mail et du mot de passe respectivement au premier Converter(UsernameCorrectConverter) et à ce celui-ci(PasswordCorrectConverter) .

using System;
using System.Globalization;
using Xamarin.Forms;

namespace FormValidation.Converters
{
    /// <summary>
    /// Converter that check if passsword is correct and return true, otherwise return false.
    /// </summary>
    public class PasswordCorrectConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return IsPasswordCorrect(value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private bool IsPasswordCorrect(object value)
        {
            if (value is string)
            {
                int length = ((string)value).Trim().Length;
                if (length >= 8)
                    return true;
                else
                    return false;
            }
            return false;
        }
    }
}
  • Le dernier convertisseur qui jouera le même rôle que celui du deuxième mais cette fois-ci pour le label d’erreur du mot de passe:
using System;
using System.Globalization;
using Xamarin.Forms;

namespace FormValidation.Converters
{
    /// <summary>
    /// Converter that check if password is correct and return true, otherwise return false.
    /// The idea is to be able to show or hide password error label.
    /// </summary>
    class PasswordCorrectToHideLabelConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return IsPasswordCorrect(value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private bool IsPasswordCorrect(object value)
        {
            if (value == null || ((string)value).Length == 0)
                return true;

            if (value is string)
            {
                int length = ((string)value).Trim().Length;
                if (length >= 8)
                    return true;
                else
                    return false;
            }
            return false;
        }
    }
}

Page d’authentification

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="FormValidation.FormsPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:controls="clr-namespace:FormValidation.Controls"
    xmlns:converters="clr-namespace:FormValidation.Converters">

    <!--  RESSOURCES  -->
    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:UsernameCorrectConverter x:Key="UsernameCorrectConverter" />
            <converters:UsernameCorrectToHideLabelConverter x:Key="UsernameCorrectToHideLabelConverter" />
            <converters:PasswordCorrectConverter x:Key="PasswordCorrectConverter" />
            <converters:PasswordCorrectToHideLabelConverter x:Key="PasswordCorrectToHideLabelConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>


    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" />
    </ContentPage.Padding>


    <StackLayout VerticalOptions="Center">
        <!--  COMPANY LOGO  -->
        <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
            <controls:DashBoardButton
                Margin="0"
                Padding="0"
                HorizontalOptions="Center"
                Icon="Shopping.png"
                Label="SHOPPING CENTER" />
        </StackLayout>

        <!--  FORM CONTAINER  -->
        <StackLayout
            Padding="20"
            Spacing="20"
            VerticalOptions="Center">
            <!--  ERROR LABELS  -->
            <Label
                FontSize="Small"
                HorizontalTextAlignment="Center"
                IsVisible="False"
                Text="Invalid email ..."
                TextColor="OrangeRed">
                <Label.Triggers>
                    <MultiTrigger TargetType="Label">
                        <MultiTrigger.Conditions>
                            <BindingCondition Binding="{Binding Source={x:Reference UserNameEntry}, Path=Text, Converter={StaticResource UsernameCorrectToHideLabelConverter}}" Value="False" />
                        </MultiTrigger.Conditions>
                        <Setter Property="IsVisible" Value="True" />
                    </MultiTrigger>
                </Label.Triggers>
            </Label>
            <Label
                FontSize="Small"
                HorizontalTextAlignment="Center"
                IsVisible="False"
                Text="Invalid password ..."
                TextColor="OrangeRed">
                <Label.Triggers>
                    <MultiTrigger TargetType="Label">
                        <MultiTrigger.Conditions>
                            <BindingCondition Binding="{Binding Source={x:Reference PasswordEntry}, Path=Text, Converter={StaticResource PasswordCorrectToHideLabelConverter}}" Value="False" />
                        </MultiTrigger.Conditions>
                        <Setter Property="IsVisible" Value="True" />
                    </MultiTrigger>
                </Label.Triggers>
            </Label>

            <!--  EMAIL AND PASSWORD ENTRY  -->
            <Entry
                x:Name="UserNameEntry"
                Focused="UserNameEntry_Focused"
                Keyboard="Email"
                MaxLength="60"
                Placeholder="Enter email"
                PlaceholderColor="DarkGray"
                Unfocused="UserNameEntry_Unfocused" />
            <Entry
                x:Name="PasswordEntry"
                Focused="PasswordEntry_Focused"
                IsPassword="True"
                Placeholder="Enter password"
                PlaceholderColor="DarkGray"
                Unfocused="PasswordEntry_Unfocused" />

            <!--  LOGIN BUTTON  -->
            <Button
                x:Name="LoginBt"
                BackgroundColor="#2196F3"
                Clicked="LoginBt_Clicked"
                IsEnabled="False"
                Text="Login"
                TextColor="White">
                <Button.Triggers>
                    <MultiTrigger TargetType="Button">
                        <MultiTrigger.Conditions>
                            <BindingCondition Binding="{Binding Source={x:Reference UserNameEntry}, Path=Text, Converter={StaticResource UsernameCorrectConverter}}" Value="True" />
                            <BindingCondition Binding="{Binding Source={x:Reference PasswordEntry}, Path=Text, Converter={StaticResource PasswordCorrectConverter}}" Value="True" />
                        </MultiTrigger.Conditions>
                        <Setter Property="IsEnabled" Value="True" />
                    </MultiTrigger>
                </Button.Triggers>
            </Button>

            <!--  ACTIVITYINDICATOR: IS RUNNING WHEN CLICK ON LOGIN BUTTON  -->
            <ActivityIndicator
                x:Name="ShowLoginIndicator"
                HorizontalOptions="Center"
                IsRunning="false"
                IsVisible="False"
                VerticalOptions="Center"
                Color="#ea0ea1">
                <ActivityIndicator.Triggers>
                    <MultiTrigger TargetType="ActivityIndicator">
                        <MultiTrigger.Conditions>
                            <BindingCondition Binding="{Binding Source={x:Reference UserNameEntry}, Path=Text, Converter={StaticResource UsernameCorrectConverter}}" Value="True" />
                            <BindingCondition Binding="{Binding Source={x:Reference PasswordEntry}, Path=Text, Converter={StaticResource PasswordCorrectConverter}}" Value="True" />
                        </MultiTrigger.Conditions>
                        <Setter Property="IsVisible" Value="True" />
                    </MultiTrigger>
                </ActivityIndicator.Triggers>
            </ActivityIndicator>

        </StackLayout>
    </StackLayout>
</ContentPage>

Et le tour est joué. On a un formulaire avec des validations interactives. Pas de besoin d’aller jusqu’aux ViewModels pour effectuer les vérification et afficher des tas de boîtes de dialogue.

Je vous explique le MultiTrigger du bouton de login pour que vous puissiez comprendre les autres. D’abord voici son code:

<Button.Triggers>
    <MultiTrigger TargetType="Button">
        <MultiTrigger.Conditions>
            <BindingCondition Binding="{Binding Source={x:Reference UserNameEntry}, Path=Text, Converter={StaticResource UsernameCorrectConverter}}" Value="True" />
            <BindingCondition Binding="{Binding Source={x:Reference PasswordEntry}, Path=Text, Converter={StaticResource PasswordCorrectConverter}}" Value="True" />
        </MultiTrigger.Conditions>
        <Setter Property="IsEnabled" Value="True" />
    </MultiTrigger>
</Button.Triggers>

Comme je l’ai mentionné plus haut, chaque Control(Entry, Button, Label, …) possède la fonctionnalité de Triggers. Pour un Bouton, on commence par le déclarer avec la balise <Button.Triggers></Button.Triggers> (Pour un Label, <Label.Triggers></Label.Triggers>).

Ensuite il faut préciser le type de Trigger à appliquer au Control, dans notre cas c’est un MultiTrigger. D’où la balise <MultiTrigger></MultiTrigger>.

Maintenant place aux conditions à vérifier par le Trigger dans la balise <MultiTrigger.Conditions></MultiTrigger.Conditions> . Chaque condition est définie dans la une balise BindingCondition

  1. La Source de la première condition est une référence vers le Champ de saisie de l’e-mail(Source={x:Reference UserNameEntry}). Comme un Entry(le Champ de saisie) a plusieurs propriétés, il faut donc spécifier ce que nous voulons. c’est le Text c’est-à-dire ce qui est saisi d’où le paramètre Path=Text. À ce niveau l’on a l’e-mail, il faut à présent, grâce au premier Converter, convertir l’e-mail en booléen: Converter={StaticResource UsernameCorrectConverter}}. Et enfin il faudra préciser la valeur que nous attendions pour la condition: d’ou Value="True".
  2. Le procédé est le même qu’en 1 pour le mot de passe.

On attend à ce que nos deux conditions soient vérifiées c’est-à-dire que leur Value soit à True. Et si c’est le cas, l’action doit être déclenchée. La balise Setter est la balise d’action du Trigger. Son attribut Property permet de spécifier la propriété actuelle su Bouton sur laquelle l’action sera déclenchée. Ici, nous voulons activer le bouton (IsEnable=True): Property="IsEnabled" Value="True".

Ressources

Voir la Démo sur YouTube et le Code source sur Github.

Merci pour votre attention ! Et si quelque chose ne vous semble pas être clair, n’hésitez pas à me contacter via le formulaire de contact ou par mail


Commentaires