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
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.
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 fonctionnent 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:
- 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. - 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.
- Si rien n’a été saisi dans les champs, ne pas faire apparaître les messages d’erreur.
- 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
- 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 leText
c’est-à-dire ce qui est saisi d’où le paramètrePath=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’ouValue="True"
. - 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