Partager via


Personnaliser des contrôles avec des gestionnaires

Parcourir l'exemple. Parcourir l'exemple

Les gestionnaires peuvent être personnalisés pour augmenter l’apparence et le comportement d’un contrôle multiplateforme au-delà de la personnalisation possible via 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 pour un gestionnaire avant l’application des mappages de contrôle de .NET MAUI.
  • ModifyMapping, qui modifie un mappage existant.
  • AppendToMapping, qui modifie le mappeur pour un gestionnaire après l’application des mappages de contrôle MAUI .NET.

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

  • 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 MAUI .NET sont basées sur des noms d’interface et de propriétés, par exemple nameof(IEntry.IsPassword). Vous trouverez les interfaces qui résument chaque contrôle multiplateforme dans le dépôt dotnet/maui. Il s’agit du format 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é comme une clé, avec toute modification de vue native effectuée dans le cadre de la 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 est modifié pour la première fois.
  • Un Action qui représente la méthode effectuant la personnalisation du gestionnaire. Le Action spécifie deux arguments :
    • Un argument handler qui fournit une instance du gestionnaire en cours de personnalisation.
    • 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 est autorisée à se produire n’importe où dans votre application. Une fois qu’un gestionnaire est personnalisé, il 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é est accessible pour définir des propriétés de vue native, appeler des méthodes d’affichage natif et s’abonner à des événements d’affichage natif. 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, pour du code multi-cible basé sur la plateforme. Vous pouvez également utiliser des classes partielles pour organiser votre code en dossiers et 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 associe la vue Entry aux vues natives suivantes pour chaque plateforme :

  • Catalyseur iOS/Mac : UITextField
  • Android : AppCompatEditText
  • Windows : TextBox
  • Catalyseur iOS/Mac : UITextField
  • Android : MauiAppCompatEditText
  • Windows : TextBox

Les diagrammes suivants montrent comment la vue Entry est convertie à ses vues natives via le EntryHandler.

Architecture du gestionnaire d’entrée.

Architecture du gestionnaire d’entrée.

Le Entry mappeur de propriétés, dans la EntryHandler classe, mappe les propriétés de contrôle multiplateforme à l’API de 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 Entry personnalisation se produit dans une classe de page. Par conséquent, tous les Entry contrôles sur Android, iOS et Windows seront personnalisés une fois qu’une instance du fichier CustomizeEntryPage est créée. La personnalisation se fait via l'accès à la propriété des gestionnaires PlatformView, qui permet d'accéder à la vue native associée au contrôle multiplateforme sur chaque plateforme. Le code natif personnalise ensuite le gestionnaire en sélectionnant tout le texte dans le Entry lorsqu'il obtient le focus.

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

Personnaliser une instance de contrôle spécifique

Les gestionnaires sont globaux et la personnalisation d’un gestionnaire pour un contrôle entraîne 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 subclassant 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 qui contient plusieurs Entry contrôles, vous devez d’abord sous-classer le Entry contrôle :

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

Vous pouvez ensuite personnaliser le EntryHandler, via son mappeur de propriétés, pour effectuer la modification souhaitée uniquement aux 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 App classe, toutes MyEntry les instances 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 à base de gestionnaires 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 du 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 des gestionnaires.

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 d'affichage natif, vous devez inscrire des gestionnaires d'événements pour les événements HandlerChanged et HandlerChanging sur le contrôle personnalisé multiplateforme.

<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 en dossiers et fichiers spécifiques à la plateforme. Chaque approche sera abordée à son tour en personnalisant un Entry afin que tout son texte soit sélectionné lorsqu'il reçoit 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 qui suit, utilisant 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
}
#if ANDROID
using Microsoft.Maui.Platform;
#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 MauiAppCompatEditText).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é après la vue native qui implémente le contrôle multiplateforme a été créé et initialisé. Par conséquent, son gestionnaire d’événements est l’endroit où les abonnements aux événements natifs doivent être effectués. Cela nécessite la conversion de la propriété PlatformView du gestionnaire au type ou au 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 des vues natives qui sont déclenchés lorsque les vues natives qui implémentent le Entry reçoivent 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 qui se trouve 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, son gestionnaire d’événements est l’endroit où les abonnements aux événements natifs doivent être supprimés, et d’autres nettoyages doivent être effectués. L'objet HandlerChangingEventArgs qui accompagne cet événement a les propriétés OldHandler et NewHandler, qui seront définies respectivement sur les anciens et les nouveaux gestionnaires. Dans cet exemple, l’événement OnEntryHandlerChanging supprime l’abonnement aux événements d’affichage natif 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 les dossiers enfants Plateformes 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 de méthode partielles sont ensuite définies dans les classes partielles spécifiques à la plateforme, qui doivent être placées dans les dossiers enfants plateformes correctes 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 CustomizeEntryPartialMethodsPage classe 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, 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 que vous n’ayez pas besoin de placer du code de plateforme dans les sous-dossiers du dossier Plateformes , consultez Configurer le multi-ciblage.