Share via


Personnaliser des contrôles avec des gestionnaires

Parcourez l’exemple. Parcourir l'exemple

Les gestionnaires peuvent être personnalisés pour améliorer l’apparence et le comportement d’un contrôle multiplateforme au-delà des possibilités de personnalisation offertes par l'API du contrôle. Cette personnalisation, qui modifie les vues natives pour le contrôle multiplateforme, est obtenue en modifiant le mappeur pour un gestionnaire avec l’une des méthodes suivantes :

  • PrependToMapping, qui modifie le mappeur d’un gestionnaire avant l’application des mappages de contrôle .NET MAUI.
  • ModifyMapping, qui modifie un mappage existant.
  • AppendToMapping, qui modifie le mappeur d’un gestionnaire après l’application des mappages de contrôle .NET MAUI.

Chacune de ces méthodes a une signature identique qui nécessite deux arguments :

  • Une clé basée sur string. Lors de la modification de l’un des mappages fournis par .NET MAUI, la clé utilisée par .NET MAUI doit être spécifiée. Les valeurs clés utilisées par les mappages de contrôle .NET MAUI sont basées sur des noms d’interfaces et de propriétés, par exemple nameof(IEntry.IsPassword). Les interfaces, ainsi que leurs propriétés, qui résument chaque contrôle multiplateforme sont disponibles ici. Il s’agit du format de clé à utiliser si vous souhaitez que votre personnalisation du gestionnaire s’exécute chaque fois qu’une propriété change. Sinon, la clé peut être une valeur arbitraire qui n’a pas besoin de correspondre au nom d’une propriété exposée par un type. Par exemple, MyCustomization peut être spécifié en tant que clé, avec toute modification de vue native effectuée comme personnalisation. Toutefois, une conséquence de ce format de clé est que la personnalisation de votre gestionnaire ne sera exécutée que lorsque le mappeur du gestionnaire sera modifié pour la première fois.
  • Un Action qui représente la méthode qui effectue la personnalisation du gestionnaire. Le Action spécifie deux arguments :
    • Un argument handler qui fournit une instance du gestionnaire en cours de personnalisation.
    • Un argument view qui fournit une instance du contrôle multiplateforme implémenté par le gestionnaire.

Important

Les personnalisations du gestionnaire sont globales et ne sont pas limitées à une instance de contrôle spécifique. La personnalisation du gestionnaire peut être effectuée n’importe où dans votre application. Une fois qu’un gestionnaire est personnalisé, cela affecte tous les contrôles de ce type, partout dans votre application.

Chaque classe de gestionnaire expose la vue native du contrôle multiplateforme via sa propriété PlatformView. Cette propriété peut être utilisée pour définir les propriétés de la vue native, invoquer les méthodes de la vue native et s'abonner aux événements de la vue native. En outre, le contrôle multiplateforme implémenté par le gestionnaire est exposé via sa propriété VirtualView.

Les gestionnaires peuvent être personnalisés par plateforme à l’aide de la compilation conditionnelle, afin de cibler le code en fonction de la plateforme. Vous pouvez également utiliser des classes partielles pour organiser votre code dans des dossiers et des fichiers spécifiques à la plateforme. Pour plus d’informations sur la compilation conditionnelle, consultez Compilation conditionnelle.

Personnaliser un contrôle

La vue .NET MAUI Entry est un contrôle d’entrée de texte à ligne unique qui implémente l’interface IEntry. Le EntryHandler mappe la vue Entry aux vues natives suivantes pour chaque plateforme :

  • iOS/Mac Catalyst : UITextField
  • Android : AppCompatEditText
  • Windows : TextBox

Les diagrammes suivants montrent comment la vue Entry est mappée à ses vues natives via le EntryHandler :

Architecture du gestionnaire d’entrée.

Le mappeur de propriétés Entry, dans la classe EntryHandler, mappe les propriétés de contrôle multiplateforme à l’API de la vue native. Cela garantit que lorsqu’une propriété est définie sur un Entry, la vue native sous-jacente est mise à jour en fonction des besoins.

Le mappeur de propriétés peut être modifié pour personnaliser Entry sur chaque plateforme :

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPage : ContentPage
{
    public CustomizeEntryPage()
    {
        InitializeComponent();
        ModifyEntry();
    }

    void ModifyEntry()
    {
        Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
        {
#if ANDROID
            handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
            handler.PlatformView.EditingDidBegin += (s, e) =>
            {
                handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
            };
#elif WINDOWS
            handler.PlatformView.GotFocus += (s, e) =>
            {
                handler.PlatformView.SelectAll();
            };
#endif
        });
    }
}

Dans cet exemple, la personnalisation Entry se produit dans une classe de page. Par conséquent, tous les contrôles Entry sur Android, iOS et Windows seront personnalisés une fois qu’une instance du CustomizeEntryPage sera créée. La personnalisation est effectuée en accédant à la propriété PlatformView des gestionnaires, qui permet d’accéder à la vue native mappée au contrôle multiplateforme sur chaque plateforme. Le code natif personnalise ensuite le gestionnaire en sélectionnant tout le texte dans Entry lorsqu’il obtient le focus.

Pour plus d’informations sur les mappeurs, consultez Mappeurs.

Personnaliser une instance de contrôle spécifique

Les gestionnaires sont globaux et la personnalisation d’un gestionnaire pour un contrôle entraînera la personnalisation de tous les contrôles du même type dans votre application. Toutefois, les gestionnaires pour des instances de contrôle spécifiques peuvent être personnalisés en sous-classant le contrôle, puis en modifiant le gestionnaire pour le type de contrôle de base uniquement lorsque le contrôle est du type sous-classé. Par exemple, pour personnaliser un contrôle spécifique Entry sur une page contenant plusieurs contrôles Entry, vous devez d’abord sous-classer le contrôle Entry :

namespace CustomizeHandlersDemo.Controls
{
    internal class MyEntry : Entry
    {
    }
}

Vous pouvez ensuite personnaliser EntryHandler, via son mappeur de propriétés, pour effectuer la modification souhaitée uniquement pour les instances de MyEntry :

Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
{
    if (view is MyEntry)
    {
#if ANDROID
        handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        handler.PlatformView.EditingDidBegin += (s, e) =>
        {
            handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
        };
#elif WINDOWS
        handler.PlatformView.GotFocus += (s, e) =>
        {
            handler.PlatformView.SelectAll();
        };
#endif
    }
});

Si la personnalisation du gestionnaire est effectuée dans votre classe App, toutes les instances MyEntry de l’application seront personnalisées en fonction de la modification du gestionnaire.

Personnaliser un contrôle à l’aide du cycle de vie du gestionnaire

Tous les contrôles .NET MAUI basés sur un gestionnaire prennent en charge les événements HandlerChanging et HandlerChanged. L’événement HandlerChanged est déclenché lorsque la vue native qui implémente le contrôle multiplateforme est disponible et initialisée. L’événement HandlerChanging est déclenché lorsque le gestionnaire de contrôle est sur le point d’être supprimé du contrôle multiplateforme. Pour plus d’informations sur les événements de cycle de vie des gestionnaires, consultez Cycle de vie du gestionnaire.

Le cycle de vie du gestionnaire peut être utilisé pour effectuer la personnalisation du gestionnaire. Par exemple, pour vous abonner et vous désabonner des événements de la vue native, vous devez enregistrer des gestionnaires d'événements pour les événements HandlerChanged et HandlerChanging sur le contrôle multiplateforme en cours de personnalisation :

<Entry HandlerChanged="OnEntryHandlerChanged"
       HandlerChanging="OnEntryHandlerChanging" />

Les gestionnaires peuvent être personnalisés par plateforme à l’aide de la compilation conditionnelle ou à l’aide de classes partielles pour organiser votre code dans des dossiers et fichiers spécifiques à la plateforme. Chaque approche sera discutée à tour de rôle, en personnalisant un Entry afin que tout son texte soit sélectionné lorsqu’il obtient le focus.

Compilation conditionnelle

Le fichier code-behind contenant les gestionnaires d’événements pour les événements HandlerChanged et HandlerChanging est illustré dans l’exemple suivant, qui utilise la compilation conditionnelle :

#if ANDROID
using AndroidX.AppCompat.Widget;
#elif IOS || MACCATALYST
using UIKit;
#elif WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
#endif

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryHandlerLifecyclePage : ContentPage
{
    public CustomizeEntryHandlerLifecyclePage()
    {
        InitializeComponent();
    }

    void OnEntryHandlerChanged(object sender, EventArgs e)
    {
        Entry entry = sender as Entry;
#if ANDROID
        (entry.Handler.PlatformView as AppCompatEditText).SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        (entry.Handler.PlatformView as UITextField).EditingDidBegin += OnEditingDidBegin;
#elif WINDOWS
        (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
#endif
    }

    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e)
    {
        if (e.OldHandler != null)
        {
#if IOS || MACCATALYST
            (e.OldHandler.PlatformView as UITextField).EditingDidBegin -= OnEditingDidBegin;
#elif WINDOWS
            (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
#endif
        }
    }

#if IOS || MACCATALYST                   
    void OnEditingDidBegin(object sender, EventArgs e)
    {
        var nativeView = sender as UITextField;
        nativeView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
    }
#elif WINDOWS
    void OnGotFocus(object sender, RoutedEventArgs e)
    {
        var nativeView = sender as TextBox;
        nativeView.SelectAll();
    }
#endif
}

L’événement HandlerChanged est déclenché lorsque la vue native qui implémente le contrôle multiplateforme est disponible et initialisée. Par conséquent, c'est dans son gestionnaire d'événements que les abonnements aux événements natifs doivent être effectués. Cela nécessite de caster la propriété PlatformView du gestionnaire sur le type, ou le type de base, de la vue native afin que les événements natifs soient accessibles. Dans cet exemple, sur iOS, Mac Catalyst et Windows, l’événement OnEntryHandlerChanged s’abonne aux événements de la vue native qui sont déclenchés lorsque les vues natives implémentant le contrôle Entry obtiennent le focus.

Les gestionnaires d’événements OnEditingDidBegin et OnGotFocus accèdent à la vue native pour le Entry sur leurs plateformes respectives, puis sélectionnent tout le texte figurant dans le Entry.

L’événement HandlerChanging est déclenché avant la suppression du gestionnaire existant du contrôle multiplateforme et avant la création du nouveau gestionnaire pour le contrôle multiplateforme. Par conséquent, c'est dans son gestionnaire d'événements que les abonnements aux événements natifs devraient être supprimés, et que toute autre opération de nettoyage devrait être effectuée. L’objet HandlerChangingEventArgs qui accompagne cet événement possède les propriétés OldHandler et NewHandler, qui seront définies respectivement sur les anciens et nouveaux gestionnaires. Dans cet exemple, l’événement OnEntryHandlerChanging supprime l’abonnement aux événements de la vue native sur iOS, Mac Catalyst et Windows.

Classes partielles

Au lieu d’utiliser la compilation conditionnelle, il est également possible d’utiliser des classes partielles pour organiser votre code de personnalisation de contrôle en dossiers et fichiers spécifiques à la plateforme. Avec cette approche, votre code de personnalisation est séparé en une classe partielle multiplateforme et une classe partielle spécifique à la plateforme :

  • La classe partielle multiplateforme définit généralement les membres, mais ne les implémente pas et est conçue pour toutes les plateformes. Cette classe ne doit pas être placée dans l’une des plateformes dossiers enfants de votre projet, car cela en ferait une classe spécifique à la plateforme.
  • La classe partielle spécifique à la plateforme implémente généralement les membres définis dans la classe partielle multiplateforme et est conçue pour une plateforme unique. Cette classe doit être placée dans le dossier enfant du dossier Plateformes pour votre plateforme choisie.

L’exemple suivant montre une classe partielle multiplateforme :

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPartialMethodsPage : ContentPage
{
    public CustomizeEntryPartialMethodsPage()
    {
        InitializeComponent();
    }

    partial void ChangedHandler(object sender, EventArgs e);
    partial void ChangingHandler(object sender, HandlerChangingEventArgs e);

    void OnEntryHandlerChanged(object sender, EventArgs e) => ChangedHandler(sender, e);
    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e) => ChangingHandler(sender, e);
}

Dans cet exemple, les deux gestionnaires d’événements appellent des méthodes partielles nommées ChangedHandler et ChangingHandler, dont les signatures sont définies dans la classe partielle multiplateforme. Les implémentations des méthodes partielles sont ensuite définies dans les classes partielles spécifiques à la plateforme, qui doivent être placées dans les dossiers enfants Plateformes appropriés pour s’assurer que le système de génération tente uniquement de générer du code natif lors de la génération pour la plateforme spécifique. Par exemple, le code suivant montre la classe CustomizeEntryPartialMethodsPage dans le dossier Plateformes>Windows du projet :

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace CustomizeHandlersDemo.Views
{
    public partial class CustomizeEntryPartialMethodsPage : ContentPage
    {
        partial void ChangedHandler(object sender, EventArgs e)
        {
            Entry entry = sender as Entry;
            (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
        }

        partial void ChangingHandler(object sender, HandlerChangingEventArgs e)
        {
            if (e.OldHandler != null)
            {
                (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
            }
        }

        void OnGotFocus(object sender, RoutedEventArgs e)
        {
            var nativeView = sender as TextBox;
            nativeView.SelectAll();
        }
    }
}

L’avantage de cette approche est que la compilation conditionnelle n’est pas nécessaire et que les méthodes partielles n’ont pas besoin d’être implémentées sur chaque plateforme. Si une implémentation n’est pas fournie sur une plateforme, alors la méthode et tous les appels à la méthode sont supprimés au moment de la compilation. Pour plus d’informations sur les méthodes partielles, consultez Méthodes partielles.

Pour plus d’informations sur l’organisation du dossier Plateformes dans un projet .NET MAUI, consultez Classes et méthodes partielles. Pour plus d’informations sur la configuration du multi-ciblage afin de ne pas avoir à placer le code de la plateforme dans des sous-dossiers du dossier Plateformes, consultez Configurer le multi-ciblage.