Dot.Blog

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

Xamarin.Forms et Effet visuel disabled

L’état indisponible est géré par certains OS, certains contrôles ayant une ICommand, mais tout cela n’est pas généralisé, or le besoin est bien plus vaste, comment le résoudre une bonne fois pour toute et simplement ?

Disabled or not ?

D’abord fixons le décor, la notion de “disabled” est souvent associée aux boutons pour montrer qu’une commande n’est pas disponible à un instant donné. Même quand l’OS le gère, que l’objet bouton le gère et qu’on a relié tout cela avec une ICommand pourvue d’une expression ou d’une méthode indiquant sa disponibilité (ouf !) cela reste un contexte bien limité !

La disponibilité visuelle d’un élément d’UI est essentielle à une bonne UX

Et les boutons ne sont pas les seuls concernés par cette problématique, ni même les seuls objets possédant une ICommand qui doit être bien programmée (aec un CanExecute en particulier). Tout peut être actif ou inactif, un dessin, une ligne de séparation, un tableau, une liste, enfin tout !

Généralement cela dépendra au minimum d’une valeur booléenne dans le ViewModel permettant de savoir si tel groupe d’objets visuels ou tel autre groupe (ou un élément seul) doit être à un moment donné disponibles ou non.

Cette indisponibilité visuelle peut fort bien se régler par un changement de couleur, la couleur aussi est bindable mais quelle horreur d’avoir en MVVM une propriété exposant un type lié à l’UI exclusivement (le type Color n’a pas de sens ailleurs que dans l’UI). De plus tous les objets ne sont pas de la même couleur, il faudra se rappeler pour chacun de la couleur qu’il avait “avant” afin de la rétablir “après” le changement d’état. Bien trop fouillis !

Ce n’est pas la meilleure solution donc. Et puis, partant d’un booléen un changement de couleur devient plus complexe. On peut jouer sur les styles, on peut même utiliser le Visuel State Manager (voir mes 3 vidéos sur le sujet ! partie 1: https://youtu.be/OFcopVPrgDg ; partie 2 : https://youtu.be/yOz89aw4K6Y et partie 3 : https://youtu.be/Yrf_hHqq42Q ). Mais là encore ce n’est pas le plus pratique ni le plus simple si on vise en plus la réutilisation du code.

Alors comment résoudre de façon générique le problème ?

Parfois (et même souvent à mon avis) les solutions les meilleures sont les plus simples. Peut-être même que le seul et unique principe à connaître et à appliquer à 100% tout le temps en informatique est celui-là : KISS !

Tout le reste n’est que philosophie et peut se discuter, KISS est un concept universel.

Avec KISS on est modeste, on n’affirme aucune Vérité absolue, c’est applicable à tout, tout le temps et partout. KISS est universel. KISS n’est donc qu’amour et paix (et même “bisou” dans notre langue, si ça ce n’est pas une preuve !) Smile

Bref, il faut une solution simple. Pas par dogme, mais parce que cette simplicité ouvre la porte à une meilleure réutilisation dans plein de contextes différents même ceux auxquels on n’a pas pensé et qu’un code simple c’est de la Dette Technique en moins !

Le présent article semble par ses digressions s’écarter de son objectif. Erreur. Grossière erreur.

Ce que je cherche à vous transférer c’est un savoir-faire et un “savoir-penser le développement”. Je me fiche en effet des boutons disabled comme de ma première ligne de code en assembleur 6502 de mon Apple II …

Cet article a pour véritable sujet cet '”art du développement” et non tel ou tel point technique, Dot.Blog n’est pas un livre de recettes. C’est avant tout une transmission de savoir. Et ce savoir se transmet par des mots et non des lignes de code.

Donc prêtez attention à ces passage “non techniques” bien plus qu’aux extraits de code !

Mais je vous sens avides de code… alors parlons-en …

La meilleure façon en respectant KISS de régler le problème est de créer un convertisseur. Il prendra en entrée un booléen, et donnera en sortie un nombre.

Pourquoi “un nombre”, terme très générique, et non pas par exemple un pourcentage qui pourrait être appliquer à l’opacité d’un objet ? Tout .. simplement… parce qu’un pourcentage n’est qu’un nombre un peu particulier et qu’en autorisant tout nombre on ouvre là encore plus de possibilité et de réutilisation de code. La simplicité paye toujours.

Un exemple

Prenons un exemple je sais que vous aimez ça. Et c’est plus parlant en effet. Donc prenons quelque chose de vraiment évident, un bouton pour démarrer un traitement par exemple. Il ressemblera à cela :

image

On souhaite que son indisponibilité soit marquée par une diminution de l’opacité, ce qui est différent d’un grisé mais qui peut être plus esthétique. Donc il sera peut être rendu légèrement transparent de cette façon :

image

Cela n’interdit pas de bien gérer la commande et son CanExecute bien entendu… la couleur n’agit par magie sur votre code ! Par contre elle va agir sur le cerveau de l’utilisateur. Moins visible, à vous de régler le seuil selon le contexte visuel, elle attirera moins l’œil et fera comprendre qu’appuyer sur le bouton n’aura pas d’effet.

Alors à quoi va ressembler le code XAML de ce bouton ?

 <Button 
Opacity="{Binding CanStart, Converter={StaticResource EnabledToOpacity}}" Command="{Binding StartCommand}" ... Text="START" ... />

On le voit l’astuce est bien dans le convertisseur…

C’est simple un convertisseur, facile à écrire, facile à déclarer, facile à utiliser.

Justement dans le code XAML comment a-t-il été déclaré ?

 <converters:BoolToDecimalConverter 
x:Key="EnabledToOpacity" True="1" False="0.45" />

C’est à dire que notre convertisseur générique de booléens vers un nombre possède deux propriétés : la valeur à retourner quand le booléen passé est à true, et la valeur quand il est à false… Dans le cas qui nous intéresse ici nous allons le lier à l’opacité de certains éléments, autant le déclarer avec un nom qui a du sens dans le contexte (“EnabledToOpacity”) mais nous le voyons la classe porte un nom bien plus neutre.

Résumons, l’opacité du bouton est liée par binding à une propriété du ViewModel qui s’appelle “CanStart” et qui indique si l’action peut être lancée ou non. Comme CanStart est un booléen et que l’opacité est un double nous utilisons un convertisseur, un peu spécial, auquel nous avons attribué des valeurs pour True et False qui sont compatibles avec une opacité (au passage CanStart sera utilisée en interne, directement ou non par le ViewModel pour le CanExecute du bouton afin que tout soit cohérent et géré par un même indicateur d’état).

Pour le reste le bouton est programmé comme on le souhaite, et il est bindé à une commande bien évidemment.

Comment le convertisseur est-il conçu ?

Le voilà le gros bout de code attendu… même si je le répète ce n’est pas le plus important de cet article.

public class BoolToDecimalConverter : IValueConverter
   {
      public double True { get; set; }

      public double False { get; set; }

      public object Convert(object value, Type targetType, 
                            object parameter, 
System.Globalization.CultureInfo culture) { if (value == null) { return False; } if (value.GetType() != typeof(bool)) { return False; } return ((bool)value) ? True : False; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value; } }

Ce code n’a rien de spécial, il est même un peu moche, c’est fait exprès, c’est un convertisseur comme on peut en écrire plein. Je n’ai pas même pris la peine de coder le ConvertBack mais il pourrait l’être. En comparant la valeur passée aux seuils True et False on pourrait retour true ou false sous forme de valeur booléenne. Mais il y a pas mal de petits détails à voir. Comme le fait de savoir si on teste sur l’égalité parfaite ou juste sur un seuil. Sachant que pour un pourcentage cela serait assez facile, en fait non. On ne sait pas comment l’utilisateur va attribuer les valeurs à True et False… dans quel sens elles sont placées notamment par rapport à 0, et quel sens (sémantique) il aura donné à ces valeurs, l’impact que cela aura sur son code, et si ce n’est pas un pourcentage, deux nombres juste comme ça, Pi et Log(9) par exemple; on retourne quoi ?

Bref, restons… SIMPLE oui, ça rentre ! Smile

L’intérêt d’être simple

En restant simple et générique notre convertisseur permettra la même approche mais sur d’autres objets, la couleur n’est qu’un nombre par exemple, on pourra ainsi fixer True et False (côté XAML donc respect de MVVM) pour retourner du bleu et rouge ou n’importe quoi d’autre. On pourra aussi utiliser notre convertisseur pour changer la position d’un objet à l’écran, etc…

Les utilisations possibles sont très nombreuses. Juste avec un bout de code et en cherchant à rester simple.

Conclusion

Certes la simplicité ne peut être un but en soi, le but est de faire le job… La simplicité est un concept, une direction à garder en tête, un cap mais ce n’est pas le port d’arrivée.

Face aux toolkits MVVM, aux extensions de XAML comme le Visual State Manager, les définitions de Style imbriquées, ou tout ce qui concerne la conception du code lui-même, la simplicité doit rester votre guide. Mais la destination à atteindre est le soft terminé, fonctionnel et offrant une bonne UX.

Cela passe souvent par une belle UI aussi car elle joue le rôle de passeur entre Code et UX.

Sur ce je vous KISS (enfin c’est symbolique hein… !)

et Stay Tuned !

blog comments powered by Disqus