Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

.NET MAUI : Focus et Ordre de saisie des Entry

Une application professionnelle doit répondre à de nombreux critères, et l'un des premiers est la gestion du Focus. Voyons cela de plus près dans le contexte de .NET MAUI...

Le Focus

C’est tout bête, le focus, tellement qu’on n’y prête pas assez attention. Pourtant, c’est l’un des critères les plus importants pour juger si une application est professionnelle ou non, si elle est terminée ou pas. Et même de façon inconsciente.

Nota: j’ai déjà traité ce sujet en 2020 pour les Xamarin.Forms mais bien que les similarités avec MAUI soient très grandes, il y a beaucoup de petites différences dans l’implémentation de certaines features. La gestion du focus en fait partie, ce qui justifie la réécriture de l’article et de ses exemples de code, totalement différents.

Prenons un exemple quotidien : les premières versions du site web “Deepl”, un traducteur de qualité supérieure à Google selon certains. Deepl incitait à installer une application Windows Store équivalente. Après l’avoir essayée, on constatait que l’application mettait autant de temps à se lancer que le site web, ce qui rendait l’application plutôt inutile, heureusement à ce niveau les choses se sont améliorées. Mais au-delà de cette lenteur, l’application s’affichait et on commençait à taper ou à faire un Ctrl-V, et là, deux possibilités : soit rien ne se passait, soit il se passait des choses vraiment bizarres.

Raison : l’application s’affichait mais elle ne prenait pas correctement  le focus que Windows lui donnait. Forcément, avec de la chance il ne se passait rien, l'application n'avait tout simplement pas le focus, soit il se passait des choses étranges, et pour cause, ce qu'on tapait ou collait allait vers l'application précédente qui avait gardé le focus... Destruction de contenu, envoi de commandes plaçant l'autre application dans un état non désiré, bref la catastrophe. Tout cela pour une gestion de focus mal gérée. Alors retenez bien ce dicton totalement inventé “Focus mal géré, Appli pétée !”

De nombreuses applications gèrent mal le focus : le premier champ focussé n’est pas le premier champ de saisie, lorsqu’on valide une entrée le focus ne bouge pas ou va n’importe où, etc. L’effet sur l’utilisateur est désastreux. Bref, dans la vie, on évite les focus (en deux mots), en informatique, on a intérêt à les soigner !

Première leçon à retenir : faites en sorte que le premier champ ayant le focus dans une page soit le plus pertinent.

J'entends bien quelques voix qui me parlent de smartphone sur lesquels l'utilisateur tape avec son doigt et n'utilise pas la touche Entrée pour valider ses saisies comme sur un vieux terminal. Je veux bien. Je leur rappellerai que MAUI est cross-plateforme, et que les Apps tournent aussi sur Mac et sur PC. Et même sur smartphone le rôle de la touche Entrée s'avère important pour éviter à l'utilisateur de faire un dismiss du clavier pour voir et tapoter sur le champ suivant qu'il ne peut pas atteindre sinon... Donc même pour des Apps smartphone ou tablette, la gestion du focus est essentielle !

L’ordre de saisie

Lorsque plusieurs champs s’enchaînent dans une saisie, comme “nom”, “prénom”, “adresse”, etc., il est crucial que le focus soit bien géré. Ce n’est plus la nature statique du focus qui nous intéresse ici mais sa dynamique : comment il se promène de champ en champ.

Les environnements de développement proposent en général un moyen de fixer l’ordre des contrôles visuels dans la chaîne des focus. Sous .NET MAUI, la gestion du focus est facilitée par la propriété `Entry.ReturnCommand` et le comportement `SetFocusOnEntryCompletedBehavior` du Toolkit MAUI.

Utilisation de la propriété Entry.ReturnCommand

`Entry.ReturnCommand` est une `ICommand` qui peut exécuter du code lorsqu’on appuie sur Entrer (ou son équivalent virtuel). C’est très pratique pour s’assurer que la dynamique du focus est correcte et que l’ordre de saisie a un sens.

Option 1 : Gestion du Focus avec ReturnCommand en Code-Behind et en XAML

Voici comment gérer le focus en utilisant la propriété `ReturnType` en XAML :

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SampleApp.EntryFocusPage">
    <StackLayout Padding="15,25">
        <Entry x:Name="Entry_First" ReturnType="Next" Placeholder="First Entry" HeightRequest="45"/>
        <Entry x:Name="Entry_Second" ReturnType="Next" Placeholder="Second Entry" HeightRequest="45"/>
        <Entry x:Name="Entry_Third" ReturnType="Done" Placeholder="Third Entry" HeightRequest="45"/>
        <BoxView HeightRequest="20"/>
        <Button Text="Save" VerticalOptions="End"/>
    </StackLayout>
</ContentPage>

 

Ici il suffit de nomer les champs de saisie et d'indiquer dans chacun le nom du champ suivant. L'appui sur Entrer enverra le focus là où il faut.

Et si on désire le faire dans le code-behind (ce qui est licite en MVVM puisqu'il s'agit de code d'UI exclusivement) :

using Microsoft.Maui.Controls;

namespace SampleApp
{
    public partial class EntryFocusPage : ContentPage
    {
        public EntryFocusPage()
        {
            InitializeComponent();

            Entry_First.ReturnCommand = new Command(() => Entry_Second.Focus());
            Entry_Second.ReturnCommand = new Command(() => Entry_Third.Focus());
        }
    }
}

Avec ce code, appuyer sur Entrer sur les premiers et deuxièmes Entry entraînera le passage du focus au champ suivant.

Focus d'entrée

C'est le plus simple à gérer. Lorsqu'une page de votre App s'ouvre n'oubliez jamais de positionner le focus sur le 1er champ ayant un intérêt. La plupart des développeurs Web ne le font pas et on s'énerve, montrez-leur comment travaille un vrai pro...

protected override void OnAppearing()
{
    base.OnAppearing();
    monChamp.Focus();
}

Aussi simple que cela, sur le OnAppearing de la page, placez le focus sur le bon champ. C'est facile, et ça change tout. Vous pouvez choisir de réagir à d'autres évènements car ici, si la page en appelle une autre et qu'ensuite l'utilisateur revient sur elle, le focus sera de nouveau placé sur "monChamp", ce qui n'est pas le plus intelligent. Mais selon votre App, à vous d'adapter ce comportement pour qu'il soit pratique et aide l'utilisateur.

On peut bien entendu faire la même chose en XAML :

<Entry x:Name="monChamp" Focus="True" />

Option 2 : Gestion du Focus avec SetFocusOnEntryCompletedBehavior en XAML

Une alternative à l'utilisation de la propriété `ReturnCommand` est d'utiliser le comportement `SetFocusOnEntryCompletedBehavior` du Toolkit MAUI. Cette méthode permet de gérer le focus directement dans le XAML sans avoir à écrire de code-behind.

Pour utiliser ce comportement, il faut d'abord installer le package NuGet `CommunityToolkit.Maui`.

Voici un exemple d’utilisation en XAML :

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="SampleApp.EntryFocusPage">
    <StackLayout Padding="15,25">
        <Entry x:Name="Entry_First" Placeholder="First Entry" HeightRequest="45">
            <Entry.Behaviors>
                <toolkit:SetFocusOnEntryCompletedBehavior TargetEntry="{x:Reference Entry_Second}" />
            </Entry.Behaviors>
        </Entry>
        <Entry x:Name="Entry_Second" Placeholder="Second Entry" HeightRequest="45">
            <Entry.Behaviors>
                <toolkit:SetFocusOnEntryCompletedBehavior TargetEntry="{x:Reference Entry_Third}" />
            </Entry.Behaviors>
        </Entry>
        <Entry x:Name="Entry_Third" Placeholder="Third Entry" HeightRequest="45"/>
        <BoxView HeightRequest="20"/>
        <Button Text="Save" VerticalOptions="End"/>
    </StackLayout>
</ContentPage>

Avec ce comportement, l’appui sur Entrer dans les Entry fera automatiquement passer le focus au champ suivant défini dans le XAML.

Explication du ReturnType de l'Entry

La propriété `ReturnType` de l'Entry détermine l'apparence et le comportement de la touche de retour sur le clavier virtuel. Les valeurs possibles sont :

- Default : Utilise la valeur par défaut du clavier.

- Done : Affiche une touche de retour indiquant que l’action est terminée (utile pour le dernier champ de saisie).

- Go : Affiche une touche permettant de lancer une action (utile pour des recherches ou des envois).

- Next : Indique que l’utilisateur doit passer au champ suivant (très utile pour gérer le focus entre plusieurs champs).

- Search : Affiche une touche de recherche (utile pour les barres de recherche).

- Send : Affiche une touche d’envoi (utile pour les formulaires de contact ou de messagerie).

Adaptation à des conditions changeantes

La gestion du focus doit s’adapter à des conditions de saisie changeantes. Par exemple, dans une fiche Contact, si l’utilisateur coche "marié", le champ "nom du conjoint" devient pertinent et doit recevoir le focus. Sinon, ce champ doit être ignoré.

Avec .NET MAUI, cette adaptation peut être gérée en code-behind ou avec des comportements personnalisés.

Jouer avec le clavier virtuel

Pour aller plus loin, on peut personnaliser la touche Entrée du clavier virtuel pour qu’elle indique "Suivant" ou "Terminé" selon le contexte. Cela améliore l’expérience utilisateur en rendant le processus de saisie plus intuitif.

Sous .NET MAUI, les PlatformEffect peuvent être utilisés pour personnaliser le comportement des contrôles de manière spécifique à chaque plateforme. La migration des effets de Xamarin.Forms vers .NET MAUI nécessite quelques ajustements , n'hésitez pas à consulter la documentation officielle pour vous assurer d'appliquer la bonne méthode.

Exemple de classe combinée FocusRoutingEffect pour Android, iOS et Windows :

using Microsoft.Maui.Controls.Platform;
namespace MyMauiApp.Effects
{
    internal class FocusRoutingEffect : RoutingEffect
    {
        public FocusRoutingEffect() : base("MyMauiApp.FocusRoutingEffect") { }
    }
#if ANDROID
    internal class FocusPlatformEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            if (Control is Android.Widget.EditText editText && Element is Entry entry)
            {
                editText.ImeOptions = Android.Views.InputMethods.ImeAction.Next;
                editText.EditorAction += (sender, args) =>
                {
                    if (args.ActionId == Android.Views.InputMethods.ImeAction.Next)
                    {
                        entry.Focus();
                    }
                };
            }
        }
        protected override void OnDetached() { }
    }
#elif IOS
    internal class FocusPlatformEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            if (Control is UIKit.UITextField textField && Element is Entry entry)
            {
                textField.ReturnKeyType = UIKit.UIReturnKeyType.Next;
                textField.ShouldReturn += (tf) =>
                {
                    entry.Focus();
                    return false;
                };
            }
        }
        protected override void OnDetached() { }
    }
#elif WINDOWS
    internal class FocusPlatformEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            // Personnalisez le contrôle ici pour Windows
        }
        protected override void OnDetached() { }
    }
#endif
}
```

Cette classe combine les implémentations spécifiques à chaque plateforme. Sous Android, elle modifie l’option Ime du clavier et attache un gestionnaire d'événements pour passer au contrôle suivant. Sous iOS, elle définit le type de touche de retour et attache également un gestionnaire d'événements pour passer au contrôle suivant. Encore une fois, s'agissant de plus ici d'aller titiller chaque OS, je vous conseille vivement de lire les documentations avant de vous lancer. Le code example n'est qu'une illustration de ce qu'il faut faire à l'instant T où je l'ai écrit sans autre garantie.

Conclusion

La gestion du focus est essentielle pour une application de qualité professionnelle. En .NET MAUI, des outils comme Entry.ReturnCommand, SetFocusOnEntryCompletedBehavior et les PlatformEffect simplifient cette tâche. Une bonne gestion du focus fait toute la différence entre une application amateur et une application professionnelle. Quelle que soit la device ciblée d'ailleurs.

Et... Stay Tuned!

Faites des heureux, PARTAGEZ l'article !