Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1115 minDébutant

Ergonomie mobile et bureau : React Native et WinForms

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • adapter un écran mobile au doigt, au clavier virtuel et aux lecteurs d'écran ;
  • régler l'ordre de tabulation, les raccourcis et la validation d'une fenêtre WinForms ;
  • afficher les états chargement, vide, erreur et succès dans chaque technologie.

4 exercices corrigés

Rendre ergonomique une application mobile ou de bureau, c'est appliquer les mêmes principes qu'en web avec les composants natifs de la plateforme : sur un écran tactile, des cibles assez grandes pour le doigt, le bon clavier virtuel et des rôles et des noms pour VoiceOver et TalkBack ; dans une application de bureau, un ordre de tabulation logique, des raccourcis clavier, des noms accessibles et des erreurs qui n'interrompent pas le travail. Ce chapitre montre ces réglages sur l'écran de retour de Prêt'Matos, en React Native puis en WinForms, et compare la façon dont chaque technologie affiche les états d'un écran.

Le principe reste celui du chapitre 10 : partir des composants natifs (Pressable et TextInput en React Native, Button, TextBox et ComboBox en WinForms), qui savent déjà recevoir le focus et se présenter aux lecteurs d'écran, puis régler les quelques propriétés que les valeurs par défaut laissent de côté.

En React Native : penser au doigt et au lecteur d'écran mobile

Sur un téléphone ou une tablette, trois contraintes s'ajoutent : le doigt est moins précis que la souris, le clavier virtuel cache la moitié de l'écran, et les lecteurs d'écran (VoiceOver, TalkBack) se pilotent par gestes. Si la logistique de Sorbier Logistique avait une application native pour le comptoir du stock, l'écran de retour ressemblerait à ceci :

tsx
import { useState } from 'react';
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';

export function RetourMateriel({ onEnregistrer }: { onEnregistrer: (numero: string) => void }) {
  const [numero, setNumero] = useState('');

  return (
    <View style={styles.ecran}>
      <Text style={styles.titre} accessibilityRole="header">
        Enregistrer un retour
      </Text>
      <Text style={styles.libelle}>Numéro d'inventaire</Text>
      <TextInput
        style={styles.champ}
        value={numero}
        onChangeText={setNumero}
        accessibilityLabel="Numéro d'inventaire"
        accessibilityHint="Le numéro est sur l'étiquette du matériel, par exemple PC-07"
        autoCapitalize="characters"
        autoCorrect={false}
        returnKeyType="done"
        onSubmitEditing={() => onEnregistrer(numero)}
      />
      <Pressable
        style={({ pressed }) => [styles.bouton, pressed && styles.boutonEnfonce]}
        accessibilityRole="button"
        onPress={() => onEnregistrer(numero)}
      >
        <Text style={styles.texteBouton}>Enregistrer le retour</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  ecran: { flex: 1, padding: 16, gap: 8 },
  titre: { fontSize: 24, fontWeight: '700' },
  libelle: { fontSize: 16 },
  champ: { minHeight: 48, borderWidth: 1, borderColor: '#6b7280', borderRadius: 6, paddingHorizontal: 12, fontSize: 18 },
  bouton: { minHeight: 48, borderRadius: 6, backgroundColor: '#1d6b43', alignItems: 'center', justifyContent: 'center' },
  boutonEnfonce: { opacity: 0.8 },
  texteBouton: { color: '#ffffff', fontSize: 18, fontWeight: '700' },
});
RègleDans le code
Cibles d'au moins 44 à 48 pixelsminHeight: 48 sur le champ et le bouton ; hitSlop agrandit la zone d'une petite icône sans changer son dessin
Un rôle et un nom pour le lecteur d'écranaccessibilityRole="header" sur le titre, "button" sur le bouton ; accessibilityLabel et accessibilityHint sur le champ
Le bon clavier, sans correction automatiqueautoCapitalize="characters", autoCorrect={false} pour un code comme « PC-07 » ; keyboardType pour les nombres et les téléphones
La touche du clavier enchaînereturnKeyType="done" et onSubmitEditing : Noah valide sans chercher le bouton
Un retour visuel au toucherLe style pressed du Pressable
Les couleurs du design systemLes mêmes jetons que le Web : vert #1d6b43, contour #6b7280

Le texte de React Native suit par défaut la taille de police choisie dans les réglages du téléphone : ne bloque pas ce comportement, et vérifie que tes écrans le supportent. Le cours développement mobile détaille les composants et l'accessibilité en React Native, et son chapitre sur les formulaires gère le clavier qui recouvre les champs.

En WinForms : le clavier d'abord

Une application de gestion WinForms est utilisée au clavier toute la journée. Les contrôles natifs sont accessibles, à condition de régler quelques propriétés que le concepteur visuel laisse souvent à leur valeur par défaut. Voici l'équivalent WinForms de l'écran de retour ; ses contrôles (txtNumero, cboEtat, btnScanner, btnEnregistrer, btnAnnuler, le libellé lblErreurNumero et le composant errorProvider) ont été posés dans le concepteur :

csharp
using System.ComponentModel;
using System.Text.RegularExpressions;

public partial class FormRetour : Form
{
    public FormRetour()
    {
        InitializeComponent();

        // L'ordre de tabulation suit l'ordre de lecture, de haut en bas.
        txtNumero.TabIndex = 0;
        cboEtat.TabIndex = 1;
        btnEnregistrer.TabIndex = 2;
        btnAnnuler.TabIndex = 3;

        AcceptButton = btnEnregistrer;              // Entrée enregistre
        CancelButton = btnAnnuler;                  // Échap ferme la fenêtre
        btnAnnuler.CausesValidation = false;        // Annuler ne déclenche pas la validation
        btnEnregistrer.Text = "&Enregistrer le retour";   // raccourci Alt+E

        // Le bouton n'affiche qu'une icône : il lui faut un nom pour les lecteurs d'écran.
        btnScanner.AccessibleName = "Scanner l'étiquette";

        // Une liste fermée plutôt qu'une saisie libre, avec la valeur la plus probable.
        cboEtat.DropDownStyle = ComboBoxStyle.DropDownList;
        cboEtat.Items.AddRange(new object[] { "Bon état", "Abîmé", "À réparer" });
        cboEtat.SelectedIndex = 0;

        txtNumero.Validating += TxtNumero_Validating;
    }

    private void TxtNumero_Validating(object? sender, CancelEventArgs e)
    {
        string numero = txtNumero.Text.Trim().ToUpperInvariant();
        string message = "";
        if (numero.Length == 0)
        {
            message = "Indiquez le numéro d'inventaire, par exemple PC-07.";
        }
        else if (!Regex.IsMatch(numero, @"^[A-Z]{2}-\d{2,3}$"))
        {
            message = "Écrivez le numéro comme sur l'étiquette : deux lettres, un tiret, des chiffres (PC-07).";
        }
        // L'icône de l'ErrorProvider ne montre son message qu'au survol :
        // on l'écrit aussi sous le champ, dans un Label visible par tous.
        errorProvider.SetError(txtNumero, message);
        lblErreurNumero.Text = message;
        txtNumero.AccessibleDescription = message;  // lu par le lecteur d'écran au retour sur le champ
        // e.Cancel reste à false : on signale l'erreur sans bloquer le focus dans le champ.
    }
}
RègleDans WinForms
Ordre de tabulation logiqueTabIndex, ou menu Affichage > Ordre de tabulation dans le concepteur
Entrée et Échap qui font ce qu'on attendAcceptButton et CancelButton du formulaire
Raccourcis clavier& devant une lettre du texte d'un bouton ou d'un libellé
Nom pour les lecteurs d'écranAccessibleName (et AccessibleDescription) sur les contrôles sans texte visible
Pas de saisie libre quand les valeurs sont connuesComboBox en DropDownList, valeur par défaut
Message d'erreur visible et lisibleErrorProvider pour le repère, et un Label pour le texte
Erreur annoncée au lecteur d'écranAccessibleDescription du champ = le message
Ne pas piéger l'utilisateurLaisser e.Cancel à false : il peut quitter le champ et y revenir
Annuler sans être bloquéCausesValidation = false sur le bouton Annuler
Fenêtre qui s'adapteAnchor et Dock pour les redimensionnements, mise à l'échelle selon la résolution de l'écran

La validation à la sortie du champ (Validating) et l'ErrorProvider sont détaillés dans le cours C# : valider une saisie. Évite les MessageBox à chaque erreur de saisie : elles interrompent, disparaissent après un clic et obligent à mémoriser le message pendant qu'on corrige.

Les états, dans chaque technologie

Les états décrits dans la spécification (chapitre 9) doivent exister dans le code, et être perçus par tous :

ÉtatWeb et ReactReact NativeWinForms
ChargementTexte dans une région role="status" ; structure de la page en placeActivityIndicator avec un texte ; annonce par AccessibilityInfo.announceForAccessibilityCurseur d'attente et Label « Chargement… » ; traitement en async pour ne pas figer la fenêtre
VidePhrase et action (« Voir le catalogue »)Composant affiché quand la liste est vide (ListEmptyComponent d'une FlatList)Label à la place de la grille vide
ErreurMessage role="alert" et bouton « Réessayer »Message et bouton « Réessayer »Message dans la fenêtre, bouton « Réessayer »
SuccèsMessage de confirmation, focus logiqueMessage, puis retour à l'écran précédentMessage dans la barre d'état ou la fenêtre, sans bloquer

Pour WinForms, le cours C# montre comment garder une fenêtre réactive avec async et await pendant un chargement.

Exercices

Exercice 1 sur 4 Une fenêtre WinForms au clavier Réussi À revoir

Une fenêtre « Nouveau matériel » contient, de haut en bas : Désignation, Type, N° d'inventaire, puis les boutons Ajouter et Annuler. Le testeur constate que la touche Tab passe de Désignation à Annuler, puis à N° d'inventaire ; que la touche Entrée ne fait rien ; et qu'un bouton « ? » d'aide n'est pas annoncé par le lecteur d'écran. Quelles propriétés corriges-tu ?

Voir la réponseMasquer la réponse de l'exercice 1
  • TabIndex : 0 pour Désignation, 1 pour Type, 2 pour N° d'inventaire, 3 pour Ajouter, 4 pour Annuler (ou menu Affichage > Ordre de tabulation du concepteur).
  • AcceptButton du formulaire sur le bouton Ajouter (Entrée), et CancelButton sur Annuler (Échap).
  • AccessibleName du bouton « ? » : « Aide sur l'ajout d'un matériel ».

On peut ajouter des raccourcis (&Ajouter) et vérifier que « Type » est une liste fermée.

Tu avais trouvé ?

Exercice 2 sur 4 Un écran React Native à revoir Réussi À revoir

Voici le premier jet de l'écran mobile « Enregistrer un retour ». Relève ses défauts d'ergonomie et d'accessibilité, et propose une correction pour chacun.

tsx
<View>
  <TextInput
    placeholder="Numéro"
    keyboardType="numeric"
    value={numero}
    onChangeText={setNumero}
    style={{ height: 32 }}
  />
  <Pressable onPress={() => onEnregistrer(numero)}>
    <Image source={iconeValider} style={{ width: 24, height: 24 }} />
  </Pressable>
</View>
Voir la réponseMasquer la réponse de l'exercice 2
DéfautCorrection
Pas de libellé visible : « Numéro » en placeholder disparaît à la frappe et ne dit pas de quel numéro il s'agitUn Text « Numéro d'inventaire » au-dessus du champ, et un accessibilityLabel sur le champ
keyboardType="numeric" alors que le numéro contient des lettres (« PC-07 »)Le clavier par défaut, avec autoCapitalize="characters" et autoCorrect={false}
Champ de 32 pixels de hautminHeight: 48
Bouton réduit à une icône de 24 × 24 pixels, sans texteUn libellé visible, « Enregistrer le retour », et minHeight: 48 ; à défaut, hitSlop pour agrandir la zone tactile et un accessibilityLabel
Le lecteur d'écran n'annonce pas un boutonaccessibilityRole="button" sur le Pressable
Aucun retour au toucherLe style pressed du Pressable

La version corrigée est l'écran RetourMateriel présenté plus haut.

Tu avais trouvé ?

Exercice 3 sur 4 Le bouton caché par le clavier Réussi À revoir

Dans un exercice du chapitre 8, le bouton « Confirmer la réservation » restait caché sous le clavier de l'iPhone, et deux participants abandonnaient. Si le formulaire était écrit en React Native, quels moyens aurais-tu pour éviter ce problème ? Cites-en trois.

Voir la réponseMasquer la réponse de l'exercice 3
  • Faire remonter le contenu : envelopper le formulaire dans un KeyboardAvoidingView, avec behavior="padding" sur iOS, pour que le bas du formulaire reste visible au-dessus du clavier.
  • Permettre de faire défiler : placer le formulaire dans un ScrollView avec keyboardShouldPersistTaps="handled" ; sans ce réglage, le premier appui sur le bouton ne sert qu'à fermer le clavier.
  • Valider depuis le clavier : returnKeyType et onSubmitEditing sur chaque champ, pour passer au champ suivant, puis envoyer le formulaire sans chercher le bouton.

Teste ensuite sur un vrai iPhone et un vrai téléphone Android : les deux systèmes ne gèrent pas le clavier de la même façon. Le cours développement mobile montre ces composants en situation.

Tu avais trouvé ?

Exercice 4 sur 4 Les états d'une fenêtre WinForms Réussi À revoir

La logistique dispose aussi d'une fenêtre WinForms « Aujourd'hui » : une DataGridView y affiche les sorties et les retours du jour, chargés depuis l'API. Pendant le chargement, la fenêtre reste figée plusieurs secondes ; quand rien n'est prévu, la grille reste vide sans explication ; quand l'API ne répond pas, une MessageBox affiche « System.Net.Http.HttpRequestException ». Propose une correction pour chacun de ces trois états.

Voir la réponseMasquer la réponse de l'exercice 4
  • Chargement : appeler l'API avec async et await, pour que la fenêtre reste réactive, et afficher un Label « Chargement des mouvements du jour… » avec le curseur d'attente.
  • Vide : masquer la grille et afficher « Aucune sortie ni aucun retour prévu aujourd'hui. » : ce n'est pas une erreur, c'est une information.
  • Erreur : intercepter l'exception et afficher dans la fenêtre « Impossible de charger les mouvements du jour : vérifiez la connexion au réseau. » avec un bouton « Réessayer » ; le détail technique va dans le journal de l'application, pas à l'écran.

Les trois corrections relèvent du feedback immédiat et de la qualité des messages. La première évite aussi qu'une fenêtre figée trop longtemps soit marquée « (Ne répond pas) » par Windows.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Interfaces et ergonomie à l'E6 SLAM : méthode et cas corrigéRevoir le sommaire du cours