Contrôle personnalisé dans Xamarin.Forms - Un Stepper disposant d'un Entry


👉 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 entre Minimum et Maximum 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 au Value 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



Mes Badges


Categories

Nouveaux posts