Dot.Blog

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

Comprendre les Handlers MAUI

MAUI a introduit un nouveau procédé pour personnaliser les contrôles natifs : les « handlers » qui remplacent les « renderers ». Pourquoi un tel changement et quels avantages ?

Qu'est-ce qu'un handler .NET MAUI ?

Un handler .NET MAUI (un gestionnaire en français) est un concept architectural utilisé par .NET MAUI pour mapper les contrôles multiplateformes à leur implémentation native. Les handlers sont utilisés à la place des moteurs de rendu (renderers) de Xamarin.Forms dans .NET MAUI car ils offrent plusieurs avantages. Rapidité, simplicité d’utilisation, facilité de personnalisation, etc…

Pour mieux comprendre il est important de définir d'autres termes. Cela sera utile pour tous ceux qui souhaitent une introduction aux handlers .NET MAUI mais qui ne sont pas habitués aux Xamarin.Forms.

Un moteur de rendu Xamarin Forms (renderer) est un concept architectural utilisé sur chaque plate-forme ciblée par Xamarin Forms pour créer le contrôle approprié. Notez que chaque contrôle dans Xamarin Forms a son propre moteur de rendu.

Un mappeur, quant à lui, est un dictionnaire à l'intérieur d'un handler servant à mapper les API multiplateformes sur les API natives.

Remarque : .NET MAUI n'utilise pas les moteurs de rendu. Mais il dispose d'une couche de compatibilité qui vous permet d'utiliser vos anciens moteurs de rendu Xamarin Forms sans analyse d'assemblage ce qui est un gain.

Pourquoi passer des Renderers aux Handlers ?

Les handlers sont l'une des fonctionnalités les plus attrayantes que .NET MAUI nous apporte. Si vous êtes un ancien développeur Xamarin Forms, vous devriez rapidement comprendre pourquoi après les avoir utilisés. Ils rendent la vie beaucoup plus facile, lorsque nous voulons personnaliser les contrôles, et bien plus encore. Voici quelques avantages des handlers :

  • Performances améliorées : Avec les renderers la réflexion et l'analyse des assemblages sont utilisées pour lister et enregistrer les rendus pour tous les contrôles. Ce processus est lent et a un impact négatif sur les performances. De leur côté les handlers MAUI n'ont pas besoin de scanner les assemblages.
  • Code plus maintenable : Les handlers sont faciles à implémenter et à utiliser via le modèle de générateur d'hôte de .NET MAUI, ils peuvent facilement être ajoutés à un type de contrôle spécifique ou à chaque contrôle utilisé dans votre application.
  • Il est plus facile d'accéder et de modifier directement les contrôles natifs . Avec les moteurs de rendu Xamarin Forms on devait écrire beaucoup de code pour modifier les contrôles natifs sous-jacents. Les handlers rendent cela beaucoup plus facile.
  • Les handlers en eux-mêmes sont faciles à implémenter par rapport à tout le code redondant requis pour modifier un moteur de rendu (renderer).
  • Les contrôles natifs de chaque handlers ne sont pas encapsulés (Fast Renderers) dans une sorte de conteneur comme cela se faisait dans le passé.

Il est bien entendu possible d’aller plus loin dans les nombreux avantages des handlers mais ceux présentés ici sont déjà bien suffisants pour justifier l’abandon des renderers Xamarin.Forms.

Comprendre l'architecture du gestionnaire .NET MAUI

La documentation de .NET MAUI commence à être au point et vous pouvez bien entendu vous y référer en complément, c’est toujours une bonne idée. Mais l’autre façon la plus fiable d’accéder à toute l’information sur un code… c’est de l’étudier ! N’oubliez pas que le code de MAUI est ouvert et accessible sur GitHub.

Pour comprendre les handlers il est important de noter que les contrôles de .NET MAUI ont chacun une représentation sous forme d'interface, qui est une abstraction du contrôle natif concerné. Ces interfaces sont dérivées de l' interface IView . Chaque handler utilise ces interfaces des contrôles et ne touche le contrôle sous-jacent qu’uniquement au niveau de la plate-forme lorsqu’il mappe les propriétés. Prenons le Label comme exemple. Voici l' interface ILabel implémentée par Label MAUI.

Le code de l'interface ci-dessus et d'autres interfaces pour les contrôles disponibles dans .NET MAUI peuvent être trouvés ici . Les handlers fournissent désormais une couche d'abstraction pour ces contrôles. Le schéma ci-dessous, extrait de la documentations Microsoft, met très bien en évidence cette architecture :

Comme mentionné ci-dessus, chaque contrôle .NET MAUI hérite de l'interface appropriée. La classe qui implémente cette interface contient les propriétés pouvant être liées et les mappeurs si nécessaire. Pour notre contrôle Label, vous pouvez trouver les classes partielles implémentant l'interface ILabel ici et ici aussi .

NOTE : Nous appellerons les champs qui implémentent ces interfaces (dérivant de l'interface IView) « Vues Virtuelles ».

Le handler est ensuite chargé de prendre l'implémentation de ILabel, de créer le contrôle natif sous-jacent et de mapper ses propriétés à celles de la vue virtuelle. Voilà, une fois que vous avez compris cela les handlers deviendront vite vos amis !

Chaque Handler .NET MAUI est dérivé de la classe abstraite ViewHandler . Comme vous pouvez le voir dans l'image ci-dessous, cette classe précise que lors du ciblage des frameworks de chaque plate-forme, le TNativeView doit être un dérivé de la classe NativeView, et lors du ciblage d'un projet standard .Net, cela peut être n'importe quelle classe. Vous pouvez voir la définition d'autres méthodes abstraites comme "CreateNativeView" en parcourant le code source ici .

Le gestionnaire du Label est ensuite divisé en un ensemble de classes partielles, chacune responsable de tâches spécifiques. Nous pouvons voir ci-dessous, où le mappeur est défini, que des propriétés spécifiques sont liées aux méthodes qui effectuent le mappage. Le code source de tout cela se trouve ici.

Pour la plate-forme Android, nous avons le fichier LabelHandler.Android.cs, contenant le code où la vue native est créée. Dans notre cas, cette vue native est une « AppCompatTextView ». Il s'agit de la vue sous-jacente affichée sur l'interface utilisateur de votre application. Le gestionnaire rend cette vue accessible via une propriété appelée « NativeView ». Vous pouvez modifier cette vue à votre guise. Nous verrons cela très prochainement.

Vous pouvez également voir les implémentations des mappages . Si vous parcourez ces mappages, vous remarquerez que les méthodes d'extension sont utilisées pour mapper les propriétés des vues virtuelles à celles des vues natives.

Nous trouvons également la méthode "ConnectHandler" , où les valeurs par défaut sont définies, et c'est dans cette méthode que les événements doivent être souscrits si nécessaire.

 

Remarque : dans le cas où un événement doit être désabonné, le « DisconnectHandler » aurait été overdrivé et l'événement aurait été désabonné à l'intérieur de cette méthode.

La même chose vaut pour iOS et d'autres plates-formes.

Personnalisation des contrôles avec les handlers

La façon dont les handlers sont utilisés pour personnaliser les contrôles natifs est très facile à comprendre (normalement). Il existe un ensemble de méthodes qui vous permettent de modifier les mappages aux contrôles natifs après ou avant le mappage des propriétés. Lorsque vous lisez les tests unitaires des méthodes d'extension responsables de ces modifications, vous pouvez voir dans quel cas chaque méthode peut être utilisée.

  • Les modifications « AppendToMapping » effectuées via cette méthode seront exécutées après un mappage de propriété existant.
  • Les modifications « PrependToMapping » effectuées ici seront exécutées avant un mappage existant.
  • « ModifyMapping » modifie un mappage existant.

Toutes ces méthodes sont appelées de la même manière, elles diffèrent uniquement par le moment où les modifications seront exécutées.

Essayons maintenant un petit test réel afin de vérifier que tout cela fonctionne comme nous l’avons compris !

Modification de tous les contrôles

Ne lésinons pas, ci-dessous, nous modifions d’un coup d’un seul tout contrôle sur Android et changerons leur couleur d'arrière-plan en bleu. Pas de détail !

#if __ANDROID__

              Microsoft.Maui.Handlers.ViewHandler.ViewMapper.AppendToMapping(nameof(IView.Background), (h, v) =>

             {

                    (h.NativeView as Android.Views.View).SetBackgroundColor(Microsoft.Maui.Graphics.Colors.Red.ToNative());

             });

#endif


C’est du brutal !

Au passage vous pouvez modifier la clé de votre mappage comme vous le souhaitez. Voir l'image ci-dessous.

NOTE : Le code pour modifier votre gestionnaire peut être appelé n'importe où dans votre projet. Il n'est pas lié à une localisation précise dans votre projet.

Modification des contrôles d'un type spécifique uniquement

Essayons d’être un peu plus subtile… Ci-dessous, nous ne modifierons que les Label et leur donnerons une couleur de fond spécifique.

#if __ANDROID__

             Microsoft.Maui.Handlers.LabelHandler.LabelMapper.PrependToMapping("MyLabelMapping", (h, v) =>

             {

                    (h.NativeView as AppCompatTextView).SetBackgroundColor(Microsoft.Maui.Graphics.Colors.Yellow.ToNative());

             });

#endif

Modification d'une instance spécifique d'un contrôle

Soyons fous ! Tentons de modifier juste une instance spécifique d’un contrôle visuel. Pour se faire il faut d’abord créer une sous-classe pour ce contrôle et lui appliquer les modifications appropriées. La classe ne sera utilisée que pour un seul contrôle, c’est le but de l’exemple, mais bien entendu on a le droit de s’en servir à plusieurs endroits pour obtenir le même effet visuel… Suivons l’exemple de la documentation Microsoft :

D’abord le sous-classement :

public class MyEntry : Entry

    {

    }

Un code qui ne devrait effrayer personne ! 

Il suffit alors de modifier uniquement les objets de type MyEntry :

Microsoft.Maui.Handlers.EntryHandler.EntryMapper.AppendToMapping(nameof(IView.Background), (handler, view) =>

            {

                if (view is MyEntry)

                {

#if ANDROID

                  handler.NativeView.SetBackgroundColor(Colors.Red.ToNative());

#elif IOS

                  handler.NativeView.BackgroundColor = Colors.Red.ToNative();

                  handler.NativeView.BorderStyle = UIKit.UITextBorderStyle.Line;

#elif WINDOWS

                  handler.NativeView.Background = Colors.Red.ToNative();

#endif

                }

            });

On appréciera que ce code soit lui-même cross-plateforme et qu’il prévoit la modification pour Android, iOS et Windows. Toutefois l’utilisation des #IF tend à donner un code spaghetti assez peu lisible. Il existe une autre stratégie plus intéressante consistant à créer une première classe partielle puis à implémenter dans chaque répertoire de chaque plateforme la classe réelle, ou rien. Si rien n’est déclaré le contrôle ne sera pas modifié pour cette plateforme, sinon le code partiel sera utilisé. Ce qui est plus élégant que des #IF dans un code fourre-tout.

Conclusion

Les handlers de MAUI sont un vrai progrès par rapport aux renderers des Xamarin Forms. Ils simplifient beaucoup les choses et surtout ils évitent le balayage du code et l’utilisation de la réflexion pour recenser les personnalisations. Personnellement j’aimais bien ce principe « automatique ». Mais la réflexion a toujours un coût. Utilisée ponctuellement cela passe facilement, mais balayer tout le code d’une App avant de pouvoir l’afficher ralenti le chargement pour l’utilisateur. La nouvelle méthode est moins magique, moins automatique, mais elle est économe et plus rationnelle. Le tout avec une mise en œuvre assez simple à respecter. Le plus difficile étant de connaître les spécificités de chaque contrôle de chaque plateforme, le tour de force se trouve là et il reste à la charge du développeur…

Stay Tuned !

Faites des heureux, partagez l'article !
blog comments powered by Disqus