👉 https://github.com/egbakou/CustomXamarinStepper le lien vers le projet complet sur Github.
Dans cet article, il sera question de voir comment améliorer le comportement du Contrôle Stepper
de Xamarin.Forms en y ajoutant des éléments supplémentaires.
A la découverte de stepper
Tout d’abord, Xamarin.Forms Stepper
est utilisé pour sélectionner une valeur numérique dans une plage de valeurs.
Il se compose de deux boutons nommés avec les signes moins et plus. Ces boutons peuvent être manipulés par l’utilisateur pour sélectionner de manière incrémentielle une valeur à partir dans une plage de valeurs.
Le Stepper
définit quatre propriétés de type double
:
Increment
correspond au pas d’incrément de la valeur à sélectionner, par défaut le pas est de 1.Minimum
est la valeur minimale de la plage, avec une valeur par défaut 0.Maximum
est la valeur maximale de la plage, avec une valeur par défaut de 100.Value
est la valeur sélectionnée, comprise entreMinimum
etMaximum
et a la valeur par défaut 0.
Ces propriétés acceptent le Data Binding, ce qui signifie qu’elles peuvent être utilisées comme source de liaison dans une application qui utilise l’architecture Model-View-ViewModel (MVVM).
Le problème avec ce contrôle est que si l’utilisateur désire sélectionner une valeur trop élevée, il est obligé de cliquer sur le bouton + le nombre de fois correspondant à la valeur souhaitée.
Nous essayerons donc de corriger cela en donnant la possibilité à l’utilisateur de saisir la valeur q’il désire tout en gardant les boutons d’incrémentation et de décrémentation.
Custom control: Stepper avec un entry
Héritons notre nouveau contrôle de StackLayout
:
using System;
using Xamarin.Forms;
namespace CustomXamarinStepper.Controls
{
public class StepperWithEntry : StackLayout
{
public StepperWithEntry()
{
}
}
}
De quels éléments d’interface Xamarin a-t-on besoin pour construire notre Stepper personnalisé ?
- Un
Button
qui portera le signe “Moins”(-); - Un champ de saisie(
Entry
); - Un autre
Button
portant le signe “Plus”(+).
using System;
using Xamarin.Forms;
namespace CustomXamarinStepper.Controls
{
public class StepperWithEntry : StackLayout
{
Button PlusBtn;
Button MinusBtn;
Entry Entry;
public StepperWithEntry()
{
}
}
}
Les “BindableProperty” de notre Contrôle
Pour notre contrôle, nous définirons 3 propriétés de type int
supportant le Data Binding.
- Une propriété pour la valeur de notre contrôle:
Text
, similaire auValue
du Stepper natif de Xamarin. Sa valeur par défaut est 0. - Une autre pour la valeur minimale:
MinimumValue
avec pour valeur par défaut 0. - Et une dernière pour la valeur maximale:
MaximumValue
, par défaut elle aura pour valeur 10.
NB: J’ai délibérément choisi le type int
pour les propriétés. J’écrirai un autre article dans lequel j’utiliserai le type double
car les contrôles de validation de saisie seront différents.
J’ai également abandonné la propriété du pas d’incrément(Increment
). Je reviendrai également sur cet élément dans un autre article.
using System;
using Xamarin.Forms;
namespace CustomXamarinStepper.Controls
{
// Current stepper accept only int value.
public class StepperWithEntry : StackLayout
{
Button PlusBtn;
Button MinusBtn;
Entry Entry;
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
propertyName: "Text",
returnType: typeof(int),
declaringType: typeof(StepperWithEntry),
defaultValue: 0,
defaultBindingMode: BindingMode.TwoWay);
public static readonly BindableProperty MinimumValueProperty =
BindableProperty.Create("MinimumValue", typeof(int), typeof(StepperWithEntry), defaultValue: 0);
public static readonly BindableProperty MaximumValueProperty =
BindableProperty.Create("MaximumValue", typeof(int), typeof(StepperWithEntry), defaultValue: 10);
public int Text
{
get { return (int)GetValue(TextProperty); }
set { SetValue(TextProperty, value); OnPropertyChanged(nameof(Text)); }
}
public int MinimumValue
{
get { return (int)GetValue(MinimumValueProperty); }
set { SetValue(MinimumValueProperty, value); }
}
public int MaximumValue
{
get { return (int)GetValue(MaximumValueProperty); }
set { SetValue(MaximumValueProperty, value); }
}
public StepperWithEntry()
{
}
}
}
Créons à présent notre contrôle dans le constructeur par défaut
using System;
using Xamarin.Forms;
namespace CustomXamarinStepper.Controls
{
// Current stepper accept only int value.
public class StepperWithEntry : StackLayout
{
Button PlusBtn;
Button MinusBtn;
Entry Entry;
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
propertyName: "Text",
returnType: typeof(int),
declaringType: typeof(StepperWithEntry),
defaultValue: 0,
defaultBindingMode: BindingMode.TwoWay);
public static readonly BindableProperty MinimumValueProperty =
BindableProperty.Create("MinimumValue", typeof(int), typeof(StepperWithEntry), defaultValue: 0);
public static readonly BindableProperty MaximumValueProperty =
BindableProperty.Create("MaximumValue", typeof(int), typeof(StepperWithEntry), defaultValue: 10);
public int Text
{
get { return (int)GetValue(TextProperty); }
set { SetValue(TextProperty, value); OnPropertyChanged(nameof(Text)); }
}
public int MinimumValue
{
get { return (int)GetValue(MinimumValueProperty); }
set { SetValue(MinimumValueProperty, value); }
}
public int MaximumValue
{
get { return (int)GetValue(MaximumValueProperty); }
set { SetValue(MaximumValueProperty, value); }
}
// Update the design of this control here
public StepperWithEntry()
{
PlusBtn = new Button { Text = "+", WidthRequest = 40, FontAttributes = FontAttributes.Bold, FontSize = 16 };
MinusBtn = new Button { Text = "-", WidthRequest = 40, FontAttributes = FontAttributes.Bold, FontSize = 16 };
switch (Device.RuntimePlatform)
{
case Device.UWP:
case Device.Android:
{
PlusBtn.BackgroundColor = Color.Transparent;
MinusBtn.BackgroundColor = Color.Transparent;
break;
}
case Device.iOS:
{
PlusBtn.BackgroundColor = Color.DarkGray;
MinusBtn.BackgroundColor = Color.DarkGray;
break;
}
}
Orientation = StackOrientation.Horizontal;
PlusBtn.Clicked += PlusBtn_Clicked;
MinusBtn.Clicked += MinusBtn_Clicked;
Entry = new Entry
{
PlaceholderColor = Color.Gray,
Keyboard = Keyboard.Numeric,
HorizontalTextAlignment = TextAlignment.Center,
TextColor = Color.Green,
WidthRequest = 60,
BackgroundColor = Color.Transparent
};
Entry.SetBinding(Entry.TextProperty, new Binding(nameof(Text), BindingMode.TwoWay, source: this));
Entry.TextChanged += Entry_TextChanged;
Entry.Unfocused += Entry_Unfocused;
Children.Add(MinusBtn);
Children.Add(Entry);
Children.Add(PlusBtn);
}
}
}
Les évènements
Au click sur le Bouton “Plus”(+), incrémentons la valeur du
Text
de notre champ de saisie.private void PlusBtn_Clicked(object sender, EventArgs e) { if (Text < MaximumValue) Text++; }
Au click sur le Bouton “Moins”(-), décrémentons la valeur du
Text
de notre champ de saisie.private void MinusBtn_Clicked(object sender, EventArgs e) { if (Text > MinimumValue) Text--; else if (Text == MinimumValue) Text = MinimumValue; }
Contrôle de validation sur notre champ de saisie.
// Check if Minimum and Maximum value rules are respected. private void Entry_TextChanged(object sender, TextChangedEventArgs e) { if (!string.IsNullOrEmpty(e.NewTextValue)) { try { var entryValue = int.Parse(e.NewTextValue); if (entryValue > MaximumValue) this.Text = MaximumValue; else this.Text = entryValue; } catch(Exception) { this.Text = 0; } } }
Évitons les valeurs décimales à la sortie du champ de saisie.
// Avoid decimal values private void Entry_Unfocused(object sender, FocusEventArgs e) { var text = ((Entry)sender).Text; if (string.IsNullOrEmpty(text) || text.Contains(",")) this.Text = 0; }
Le code complet de notre contrôle
using System;
using Xamarin.Forms;
namespace CustomXamarinStepper.Controls
{
// Current stepper accept only int value.
public class StepperWithEntry : StackLayout
{
Button PlusBtn;
Button MinusBtn;
Entry Entry;
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
propertyName: "Text",
returnType: typeof(int),
declaringType: typeof(StepperWithEntry),
defaultValue: 0,
defaultBindingMode: BindingMode.TwoWay);
public static readonly BindableProperty MinimumValueProperty =
BindableProperty.Create("MinimumValue", typeof(int), typeof(StepperWithEntry), defaultValue: 0);
public static readonly BindableProperty MaximumValueProperty =
BindableProperty.Create("MaximumValue", typeof(int), typeof(StepperWithEntry), defaultValue: 10);
public int Text
{
get { return (int)GetValue(TextProperty); }
set { SetValue(TextProperty, value); OnPropertyChanged(nameof(Text)); }
}
public int MinimumValue
{
get { return (int)GetValue(MinimumValueProperty); }
set { SetValue(MinimumValueProperty, value); }
}
public int MaximumValue
{
get { return (int)GetValue(MaximumValueProperty); }
set { SetValue(MaximumValueProperty, value); }
}
public StepperWithEntry()
{
PlusBtn = new Button { Text = "+", WidthRequest = 40, FontAttributes = FontAttributes.Bold, FontSize = 16 };
MinusBtn = new Button { Text = "-", WidthRequest = 40, FontAttributes = FontAttributes.Bold, FontSize = 16 };
switch (Device.RuntimePlatform)
{
case Device.UWP:
case Device.Android:
{
PlusBtn.BackgroundColor = Color.Transparent;
MinusBtn.BackgroundColor = Color.Transparent;
break;
}
case Device.iOS:
{
PlusBtn.BackgroundColor = Color.DarkGray;
MinusBtn.BackgroundColor = Color.DarkGray;
break;
}
}
Orientation = StackOrientation.Horizontal;
PlusBtn.Clicked += PlusBtn_Clicked;
MinusBtn.Clicked += MinusBtn_Clicked;
Entry = new Entry
{
PlaceholderColor = Color.Gray,
Keyboard = Keyboard.Numeric,
HorizontalTextAlignment = TextAlignment.Center,
TextColor = Color.Green,
WidthRequest = 60,
BackgroundColor = Color.Transparent
};
Entry.SetBinding(Entry.TextProperty, new Binding(nameof(Text), BindingMode.TwoWay, source: this));
Entry.TextChanged += Entry_TextChanged;
Entry.Unfocused += Entry_Unfocused;
Children.Add(MinusBtn);
Children.Add(Entry);
Children.Add(PlusBtn);
}
// Avoid decimal values
private void Entry_Unfocused(object sender, FocusEventArgs e)
{
var text = ((Entry)sender).Text;
if (string.IsNullOrEmpty(text) || text.Contains(","))
this.Text = 0;
}
// Check if Minimum and Maximum value rules are respected.
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
if (!string.IsNullOrEmpty(e.NewTextValue))
{
try
{
var entryValue = int.Parse(e.NewTextValue);
if (entryValue > MaximumValue)
this.Text = MaximumValue;
else
this.Text = entryValue;
}
catch(Exception)
{
this.Text = 0;
}
}
}
private void MinusBtn_Clicked(object sender, EventArgs e)
{
if (Text > MinimumValue)
Text--;
else if (Text == MinimumValue)
Text = MinimumValue;
}
private void PlusBtn_Clicked(object sender, EventArgs e)
{
if (Text < MaximumValue)
Text++;
}
}
}
Test
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="CustomXamarinStepper.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:CustomXamarinStepper.Controls"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{Binding Title}"
mc:Ignorable="d">
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<controls:StepperWithEntry
HorizontalOptions="Center"
MaximumValue="{Binding MaximumValue}"
Text="{Binding Quantity}"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
Résultat
Ressources
👉 https://github.com/egbakou/CustomXamarinStepper le lien vers le projet complet sur Github.
N’hésitez pas à me contacter via le formulaire de contact ou par mail.
Commentaires