Création de contrôles personnalisés dans Xamarin.Mac

Lorsque vous utilisez C# et .NET dans une application Xamarin.Mac, vous avez accès aux mêmes contrôles utilisateur qu’un développeur travaillant dans Objective-C, Swift et Xcode . Étant donné que Xamarin.Mac s’intègre directement à Xcode, vous pouvez utiliser le Générateur d’interface de Xcode pour créer et gérer vos contrôles utilisateur (ou éventuellement les créer directement dans du code C#).

Bien que macOS offre une multitude de contrôles utilisateur intégrés, il peut arriver que vous deviez créer un contrôle personnalisé pour fournir des fonctionnalités non fournies prêtes à l’emploi ou pour correspondre à un thème d’interface utilisateur personnalisé (par exemple, une interface de jeu).

Exemple de contrôle d’interface utilisateur personnalisé

Dans cet article, nous allons aborder les principes de base de la création d’un contrôle d’interface utilisateur personnalisé réutilisable dans une application Xamarin.Mac. Il est fortement suggéré de commencer par l’article Hello, Mac , en particulier les sections Introduction to Xcode et Interface Builderet Outlets and Actions , car il couvre les concepts et techniques clés que nous allons utiliser dans cet article.

Vous pouvez également consulter la section Exposing C# classes/méthodes to Objective-C du document Xamarin.Mac Internals . Il explique les Register commandes et Export utilisées pour connecter vos classes C# à des objets et des éléments d’interface Objective-C utilisateur.

Présentation des contrôles personnalisés

Comme indiqué ci-dessus, il peut arriver que vous deviez créer un contrôle d’interface utilisateur personnalisé réutilisable pour fournir des fonctionnalités uniques à l’interface utilisateur de votre application Xamarin.Mac ou pour créer un thème d’interface utilisateur personnalisé (par exemple, une interface de jeu).

Dans ces situations, vous pouvez facilement hériter de NSControl et créer un outil personnalisé qui peut être ajouté à l’interface utilisateur de votre application via le code C# ou via le Générateur d’interface de Xcode. En hériter de NSControl votre contrôle personnalisé aura automatiquement toutes les fonctionnalités standard d’un contrôle d’interface utilisateur intégré (comme NSButton).

Si votre contrôle d’interface utilisateur personnalisé affiche simplement des informations (comme un outil graphique et graphique personnalisé), vous pouvez en hériter NSView au lieu de NSControl.

Quelle que soit la classe de base utilisée, les étapes de base pour créer un contrôle personnalisé sont les mêmes.

Dans cet article, vous allez créer un composant bascule personnalisé qui fournit un thème d’interface utilisateur unique et un exemple de création d’un contrôle d’interface utilisateur personnalisé entièrement fonctionnel.

Génération du contrôle personnalisé

Étant donné que le contrôle personnalisé que nous créons répond à l’entrée utilisateur (clics sur le bouton gauche de la souris), nous allons hériter de NSControl. De cette façon, notre contrôle personnalisé aura automatiquement toutes les fonctionnalités standard d’un contrôle d’interface utilisateur intégré et répondra comme un contrôle macOS standard.

Dans Visual Studio pour Mac, ouvrez le projet Xamarin.Mac pour lequel vous souhaitez créer un contrôle d’interface utilisateur personnalisé (ou en créer un nouveau). Ajoutez une nouvelle classe et appelez-la NSFlipSwitch:

Ajout d’une nouvelle classe

Ensuite, modifiez la NSFlipSwitch.cs classe et faites-la ressembler à ce qui suit :

using Foundation;
using System;
using System.CodeDom.Compiler;
using AppKit;
using CoreGraphics;

namespace MacCustomControl
{
    [Register("NSFlipSwitch")]
    public class NSFlipSwitch : NSControl
    {
        #region Private Variables
        private bool _value = false;
        #endregion

        #region Computed Properties
        public bool Value {
            get { return _value; }
            set {
                // Save value and force a redraw
                _value = value;
                NeedsDisplay = true;
            }
        }
        #endregion

        #region Constructors
        public NSFlipSwitch ()
        {
            // Init
            Initialize();
        }

        public NSFlipSwitch (IntPtr handle) : base (handle)
        {
            // Init
            Initialize();
        }

        [Export ("initWithFrame:")]
        public NSFlipSwitch (CGRect frameRect) : base(frameRect) {
            // Init
            Initialize();
        }

        private void Initialize() {
            this.WantsLayer = true;
            this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
        }
        #endregion

        #region Draw Methods
        public override void DrawRect (CGRect dirtyRect)
        {
            base.DrawRect (dirtyRect);

            // Use Core Graphic routines to draw our UI
            ...

        }
        #endregion

        #region Private Methods
        private void FlipSwitchState() {
            // Update state
            Value = !Value;
        }
        #endregion

    }
}

La première chose à remarquer à propos de notre classe personnalisée dans le fait que nous héritons de NSControl et utilisons la commande Register pour exposer cette classe à et au Objective-C Générateur d’interface de Xcode :

[Register("NSFlipSwitch")]
public class NSFlipSwitch : NSControl

Dans les sections suivantes, nous allons examiner le reste du code ci-dessus en détail.

Suivi de l’état du contrôle

Étant donné que notre contrôle personnalisé est un commutateur, nous avons besoin d’un moyen de suivre l’état Activé/Désactivé du commutateur. Nous gérons cela avec le code suivant dans NSFlipSwitch:

private bool _value = false;
...

public bool Value {
    get { return _value; }
    set {
        // Save value and force a redraw
        _value = value;
        NeedsDisplay = true;
    }
}

Lorsque l’état du commutateur change, nous avons besoin d’un moyen de mettre à jour l’interface utilisateur. Pour ce faire, nous forçons le contrôle à redessiner son interface utilisateur avec NeedsDisplay = true.

Si notre contrôle nécessitait plus qu’un seul état On/Off (par exemple, un commutateur multi-états avec 3 positions), nous aurions pu utiliser une énumération pour suivre l’état. Pour notre exemple, un simple bool fera l’opération.

Nous avons également ajouté une méthode d’assistance pour échanger l’état du commutateur entre Activé et Désactivé :

private void FlipSwitchState() {
    // Update state
    Value = !Value;
}

Plus tard, nous développerons cette classe d’assistance pour informer l’appelant lorsque l’état des commutateurs a changé.

Dessin de l’interface du contrôle

Nous allons utiliser des routines de dessin Core Graphic pour dessiner l’interface utilisateur de notre contrôle personnalisé au moment de l’exécution. Avant de pouvoir effectuer cette opération, nous devons activer les couches pour notre contrôle. Nous le faisons avec la méthode privée suivante :

private void Initialize() {
    this.WantsLayer = true;
    this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}

Cette méthode est appelée à partir de chacun des constructeurs du contrôle pour s’assurer que le contrôle est correctement configuré. Par exemple :

public NSFlipSwitch (IntPtr handle) : base (handle)
{
    // Init
    Initialize();
}

Ensuite, nous devons remplacer la DrawRect méthode et ajouter les routines Core Graphic pour dessiner le contrôle :

public override void DrawRect (CGRect dirtyRect)
{
    base.DrawRect (dirtyRect);

    // Use Core Graphic routines to draw our UI
    ...

}

Nous allons ajuster la représentation visuelle du contrôle lorsque son état change (par exemple, passer de Activé à Désactivé). Chaque fois que l’état change, nous pouvons utiliser la NeedsDisplay = true commande pour forcer le contrôle à redessiner pour le nouvel état.

Réponse à l’entrée utilisateur

Il existe deux façons de base d’ajouter une entrée utilisateur à notre contrôle personnalisé : remplacer les routines de gestion de la souris ou les reconnaissancesde mouvements. La méthode que nous utilisons sera basée sur les fonctionnalités requises par notre contrôle.

Important

Pour tout contrôle personnalisé que vous créez, vous devez utiliser des méthodes de remplacementoudes modules de reconnaissance de mouvement, mais pas les deux en même temps, car ils peuvent entrer en conflit l’un avec l’autre.

Gestion des entrées utilisateur avec des méthodes de remplacement

Les objets qui héritent de NSControl (ou NSView) ont plusieurs méthodes de remplacement pour gérer l’entrée de souris ou de clavier. Pour notre exemple de contrôle, nous voulons basculer l’état du commutateur entre Activé et Désactivé lorsque l’utilisateur clique sur le contrôle avec le bouton gauche de la souris. Nous pouvons ajouter les méthodes de remplacement suivantes à la NSFlipSwitch classe pour gérer cela :

#region Mouse Handling Methods
// --------------------------------------------------------------------------------
// Handle mouse with Override Methods.
// NOTE: Use either this method or Gesture Recognizers, NOT both!
// --------------------------------------------------------------------------------
public override void MouseDown (NSEvent theEvent)
{
    base.MouseDown (theEvent);

    FlipSwitchState ();
}

public override void MouseDragged (NSEvent theEvent)
{
    base.MouseDragged (theEvent);
}

public override void MouseUp (NSEvent theEvent)
{
    base.MouseUp (theEvent);
}

public override void MouseMoved (NSEvent theEvent)
{
    base.MouseMoved (theEvent);
}
## endregion

Dans le code ci-dessus, nous appelons la FlipSwitchState méthode (définie ci-dessus) pour retourner l’état Activé/Désactivé du commutateur dans la MouseDown méthode. Cela force également le contrôle à être redessiné pour refléter l’état actuel.

Gestion des entrées utilisateur avec des reconnaissances de mouvements

Si vous le souhaitez, vous pouvez utiliser des reconnaissances de mouvement pour gérer l’utilisateur qui interagit avec le contrôle. Supprimez les remplacements ajoutés ci-dessus, modifiez la Initialize méthode et faites-la ressembler à ce qui suit :

private void Initialize() {
    this.WantsLayer = true;
    this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;

    // --------------------------------------------------------------------------------
    // Handle mouse with Gesture Recognizers.
    // NOTE: Use either this method or the Override Methods, NOT both!
    // --------------------------------------------------------------------------------
    var click = new NSClickGestureRecognizer (() => {
        FlipSwitchState();
    });
    AddGestureRecognizer (click);
}

Ici, nous créons une nouvelle NSClickGestureRecognizer méthode et nous appelons notre FlipSwitchState méthode pour modifier l’état du commutateur lorsque l’utilisateur clique dessus avec le bouton gauche de la souris. La AddGestureRecognizer (click) méthode ajoute le gesture recognizer au contrôle.

Là encore, la méthode que nous utilisons dépend de ce que nous essayons d’accomplir avec notre contrôle personnalisé. Si nous avons besoin d’un accès de bas niveau à l’interaction utilisateur, utilisez les méthodes de remplacement. Si nous avons besoin de fonctionnalités prédéfinies, telles que des clics de souris, utilisez Les reconnaissances de mouvement.

Réponse aux événements de changement d’état

Lorsque l’utilisateur modifie l’état de notre contrôle personnalisé, nous avons besoin d’un moyen de répondre au changement d’état dans le code (par exemple, en effectuant une action lorsque vous cliquez sur un bouton personnalisé).

Pour fournir cette fonctionnalité, modifiez la NSFlipSwitch classe et ajoutez le code suivant :

#region Events
public event EventHandler ValueChanged;

internal void RaiseValueChanged() {
    if (this.ValueChanged != null)
        this.ValueChanged (this, EventArgs.Empty);

    // Perform any action bound to the control from Interface Builder
    // via an Action.
    if (this.Action !=null)
        NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);
}
## endregion

Ensuite, modifiez la FlipSwitchState méthode et faites-la ressembler à ce qui suit :

private void FlipSwitchState() {
    // Update state
    Value = !Value;
    RaiseValueChanged ();
}

Tout d’abord, nous fournissons un ValueChanged événement auquel nous pouvons ajouter un gestionnaire dans le code C# afin d’effectuer une action lorsque l’utilisateur modifie l’état du commutateur.

Deuxièmement, parce que notre contrôle personnalisé hérite de NSControl, il a automatiquement une action qui peut être affectée dans le Générateur d’interface de Xcode. Pour appeler cette action lorsque l’état change, nous utilisons le code suivant :

if (this.Action !=null)
    NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);

Tout d’abord, nous case activée pour voir si une action a été affectée au contrôle. Ensuite, nous appelons l’action si elle a été définie.

Utilisation du contrôle personnalisé

Avec notre contrôle personnalisé entièrement défini, nous pouvons l’ajouter à l’interface utilisateur de notre application Xamarin.Mac à l’aide du code C# ou dans le générateur d’interface de Xcode.

Pour ajouter le contrôle à l’aide du Générateur d’interface, commencez par effectuer une propre build du projet Xamarin.Mac, puis double-cliquez sur le fichier pour l’ouvrir Main.storyboard dans le Générateur d’interface pour le modifier :

Modification du storyboard dans Xcode

Ensuite, faites glisser un Custom View dans la conception de l’interface utilisateur :

Sélection d’un affichage personnalisé dans la bibliothèque

Avec l’affichage personnalisé toujours sélectionné, basculez vers l’inspecteur d’identité et remplacez la classe de la vue par NSFlipSwitch:

Définition de la classe de l’affichage

Basculez vers l’Éditeur Assistant et créez une prise pour le contrôle personnalisé (en veillant à le lier dans le ViewController.h fichier et non dans le .m fichier) :

Configuration d’un nouveau point de vente

Enregistrez vos modifications, revenez à Visual Studio pour Mac et autorisez la synchronisation des modifications. Modifiez le ViewController.cs fichier et faites en sorte que la ViewDidLoad méthode ressemble à ce qui suit :

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    // Do any additional setup after loading the view.
    OptionTwo.ValueChanged += (sender, e) => {
        // Display the state of the option switch
        Console.WriteLine("Option Two: {0}", OptionTwo.Value);
    };
}

Ici, nous répondons à l’événement ValueChanged que nous avons défini ci-dessus sur la NSFlipSwitch classe et écrivons la valeur actuelle lorsque l’utilisateur clique sur le contrôle.

Si vous le souhaitez, nous pouvons revenir au Générateur d’interface et définir une action sur le contrôle :

Configuration d’une nouvelle action

Là encore, modifiez le ViewController.cs fichier et ajoutez la méthode suivante :

partial void OptionTwoFlipped (Foundation.NSObject sender) {
    // Display the state of the option switch
    Console.WriteLine("Option Two: {0}", OptionTwo.Value);
}

Important

Vous devez utiliser l’événement ou définir une action dans le Générateur d’interface, mais vous ne devez pas utiliser les deux méthodes en même temps ou elles peuvent entrer en conflit l’une avec l’autre.

Résumé

Cet article a examiné en détail la création d’un contrôle d’interface utilisateur personnalisé réutilisable dans une application Xamarin.Mac. Nous avons vu comment dessiner l’interface utilisateur des contrôles personnalisés, les deux main façons de répondre aux entrées de la souris et de l’utilisateur et comment exposer le nouveau contrôle à Actions dans le Générateur d’interface de Xcode.