Erstellen benutzerdefinierter Steuerelemente in Xamarin.Mac

Wenn Sie mit C# und .NET in einer Xamarin.Mac-Anwendung arbeiten, haben Sie Zugriff auf die gleichen Benutzersteuerelemente wie ein Entwickler, der in Objective-C, Swift und Xcode arbeitet. Da Xamarin.Mac direkt in Xcode integriert ist, können Sie den Schnittstellen-Generator von Xcode verwenden, um Ihre Benutzersteuerelemente zu erstellen und zu verwalten (oder optional direkt in C#-Code zu erstellen).

Während macOS eine Vielzahl von integrierten Benutzersteuerelementen bietet, müssen Sie möglicherweise ein benutzerdefiniertes Steuerelement erstellen, um Funktionen bereitzustellen, die nicht sofort verfügbar sind, oder um einem benutzerdefinierten UI-Design (z. B. einer Spieloberfläche) zu entsprechen.

Beispiel für ein benutzerdefiniertes UI-Steuerelement

In diesem Artikel werden die Grundlagen zum Erstellen eines wiederverwendbaren Steuerelements für benutzerdefinierte Benutzeroberfläche in einer Xamarin.Mac-Anwendung behandelt. Es wird dringend empfohlen, zuerst den Artikel "Hello, Mac " zu bearbeiten, insbesondere die Abschnitte Einführung in Xcode und Schnittstellen-Generator sowie Outlets und Aktionen , da er die wichtigsten Konzepte und Techniken behandelt, die wir in diesem Artikel verwenden werden.

Sie können auch einen Blick auf den Abschnitt Exposing C#-Klassen/-Methoden im Objective-CDokument Xamarin.Mac Internals werfen. Darin werden die Befehle und Export erläutert, die Register zum Verknüpfen Ihrer C#-Klassen mit Objective-C Objekten und UI-Elementen verwendet werden.

Einführung in benutzerdefinierte Steuerelemente

Wie oben erwähnt, müssen Sie möglicherweise ein wiederverwendbares, benutzerdefiniertes Benutzeroberflächensteuerelement erstellen, um einzigartige Funktionen für die Benutzeroberfläche Ihrer Xamarin.Mac-App bereitzustellen oder ein benutzerdefiniertes UI-Design (z. B. eine Spieloberfläche) zu erstellen.

In diesen Situationen können Sie ganz einfach von NSControl einem benutzerdefinierten Tool erben und erstellen, das entweder der Benutzeroberfläche Ihrer App über C#-Code oder über den Xcode-Schnittstellen-Generator hinzugefügt werden kann. Durch das Erben von NSControl Ihrem benutzerdefinierten Steuerelement werden automatisch alle Standardfeatures eines integrierten Benutzeroberflächensteuerelements (z. B NSButton. ) angezeigt.

Wenn Ihr benutzerdefiniertes Benutzeroberflächensteuerelement nur Informationen anzeigt (z. B. ein benutzerdefiniertes Diagramm- und Grafiktool), sollten Sie anstelle von NSView erben NSControl.

Unabhängig davon, welche Basisklasse verwendet wird, sind die grundlegenden Schritte zum Erstellen eines benutzerdefinierten Steuerelements identisch.

In diesem Artikel wird eine benutzerdefinierte Flip Switch-Komponente erstellt, die ein eindeutiges Benutzeroberflächendesign und ein Beispiel für das Erstellen eines voll funktionsfähigen benutzerdefinierten Benutzeroberflächensteuerelements bietet.

Erstellen des benutzerdefinierten Steuerelements

Da das benutzerdefinierte Steuerelement, das wir erstellen, auf Benutzereingaben reagiert (Mausklicks mit der linken Maustaste), erben wir von NSControl. Auf diese Weise verfügt unser benutzerdefiniertes Steuerelement automatisch über alle Standardfeatures, die ein integriertes Benutzeroberflächensteuerelement besitzt, und reagiert wie ein standardmäßiges macOS-Steuerelement.

Öffnen Sie in Visual Studio für Mac das Xamarin.Mac-Projekt, für das Sie ein benutzerdefiniertes Benutzeroberflächensteuerelement erstellen möchten (oder ein neues Erstellen). Fügen Sie eine neue Klasse hinzu, und rufen Sie sie auf NSFlipSwitch:

Hinzufügen einer neuen Klasse

Bearbeiten Sie als Nächstes die NSFlipSwitch.cs Klasse, und lassen Sie sie wie folgt aussehen:

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

    }
}

Das erste, was Sie an unserer benutzerdefinierten Klasse beachten müssen, da wir den Befehl Register erben NSControl und verwenden, um diese Klasse für und den Xcode-Schnittstellen-Generator verfügbar zu Objective-C machen:

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

In den folgenden Abschnitten sehen wir uns den Rest des obigen Codes im Detail an.

Nachverfolgen des Status des Steuerelements

Da es sich bei unserem benutzerdefinierten Steuerelement um einen Schalter handelt, benötigen wir eine Möglichkeit, den Ein/Aus-Zustand des Schalters nachzuverfolgen. Wir behandeln dies mit dem folgenden Code in NSFlipSwitch:

private bool _value = false;
...

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

Wenn sich der Zustand des Switches ändert, benötigen wir eine Möglichkeit, die Benutzeroberfläche zu aktualisieren. Dazu erzwingen wir das Steuerelement, seine Benutzeroberfläche mit NeedsDisplay = trueneu zu zeichnen.

Wenn unsere Steuerung mehr als einen einzelnen Ein/Aus-Zustand erforderte (z. B. ein Mehrstatusschalter mit 3 Positionen), hätten wir eine Enume verwenden können, um den Zustand nachzuverfolgen. In unserem Beispiel wird ein einfaches Bool ausgeführt.

Außerdem wurde eine Hilfsmethode hinzugefügt, um den Zustand des Schalters zwischen Ein und Aus zu tauschen:

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

Später erweitern wir diese Hilfsklasse, um den Aufrufer darüber zu informieren, wenn sich der Schalterzustand geändert hat.

Zeichnen der Schnittstelle des Steuerelements

Wir werden Core Graphic-Zeichnungsroutinen verwenden, um die Benutzeroberfläche unseres benutzerdefinierten Steuerelements zur Laufzeit zu zeichnen. Bevor wir dies tun können, müssen wir Ebenen für unsere Steuerung aktivieren. Dies geschieht mit der folgenden privaten Methode:

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

Diese Methode wird von jedem Konstruktor des Steuerelements aufgerufen, um sicherzustellen, dass das Steuerelement ordnungsgemäß konfiguriert ist. Beispiel:

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

Als Nächstes müssen wir die DrawRect -Methode überschreiben und die Core Graphic-Routinen hinzufügen, um das Steuerelement zu zeichnen:

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

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

}

Wir passen die visuelle Darstellung für das Steuerelement an, wenn sich sein Zustand ändert (z. B. von Ein zu Aus). Jedes Mal, wenn sich der Zustand ändert, können wir den NeedsDisplay = true Befehl verwenden, um zu erzwingen, dass das Steuerelement für den neuen Zustand neu gezeichnet wird.

Reagieren auf Benutzereingaben

Es gibt zwei grundlegende Möglichkeiten, benutzereingaben zu unserem benutzerdefinierten Steuerelement hinzuzufügen: Mausbehandlungsroutinen oder Gestenerkennungsroutinen außer Kraft setzen. Welche Methode wir verwenden, basiert auf der Funktionalität, die für unser Steuerelement erforderlich ist.

Wichtig

Für jedes benutzerdefinierte Steuerelement, das Sie erstellen, sollten Sie entweder Überschreiben von MethodenoderGestenerkennungen verwenden, aber nicht beide gleichzeitig, da sie miteinander in Konflikt stehen können.

Behandeln von Benutzereingaben mit Überschreibungsmethoden

Objekte, die von (oder NSView) erben NSControl , verfügen über mehrere Überschreibungsmethoden für die Verarbeitung von Maus- oder Tastatureingaben. Für unser Beispielsteuerelement möchten wir den Zustand des Schalters zwischen Ein und Aus umkehren, wenn der Benutzer mit der linken Maustaste auf das Steuerelement klickt. Wir können der -Klasse die folgenden Außerkraftsetzungsmethoden hinzufügen, um dies NSFlipSwitch zu behandeln:

#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

Im obigen Code rufen wir die FlipSwitchState -Methode (oben definiert) auf, um den Ein/Aus-Zustand des Schalters in der MouseDown -Methode umzudrehen. Dadurch wird auch erzwungen, dass das Steuerelement neu gezeichnet wird, um den aktuellen Zustand widerzuspiegeln.

Behandeln von Benutzereingaben mit Gestenerkennung

Optional können Sie Gestenerkennungen verwenden, um den Benutzer zu behandeln, der mit dem Steuerelement interagiert. Entfernen Sie die oben hinzugefügten Außerkraftsetzungen, bearbeiten Sie die Initialize -Methode, und lassen Sie sie wie folgt aussehen:

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);
}

Hier erstellen wir eine neue NSClickGestureRecognizer Methode und rufen unsere FlipSwitchState Methode auf, um den Zustand des Schalters zu ändern, wenn der Benutzer mit der linken Maustaste darauf klickt. Die AddGestureRecognizer (click) -Methode fügt dem Steuerelement die Gestenerkennung hinzu.

Welche Methode wir verwenden, hängt wiederum davon ab, was wir mit unserem benutzerdefinierten Steuerelement erreichen möchten. Verwenden Sie die Außerkraftsetzungsmethoden, wenn sie den Zugriff auf die Benutzerinteraktion auf niedriger Ebene benötigen. Wenn wir vordefinierte Funktionen benötigen, z. B. Mausklicks, verwenden Sie Gestenerkennung.

Reagieren auf Zustandsänderungsereignisse

Wenn der Benutzer den Zustand unseres benutzerdefinierten Steuerelements ändert, benötigen wir eine Möglichkeit, auf die Zustandsänderung im Code zu reagieren (z. B. beim Klicken auf eine benutzerdefinierte Schaltfläche).

Um diese Funktionalität bereitzustellen, bearbeiten Sie die NSFlipSwitch -Klasse, und fügen Sie den folgenden Code hinzu:

#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

Bearbeiten Sie als Nächstes die FlipSwitchState -Methode, und lassen Sie sie wie folgt aussehen:

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

Zunächst stellen wir ein ValueChanged Ereignis bereit, dem wir einen Handler in C#-Code hinzufügen können, damit wir eine Aktion ausführen können, wenn der Benutzer den Zustand des Switches ändert.

Zweitens, da unser benutzerdefiniertes Steuerelement von NSControlerbt, verfügt es automatisch über eine Aktion , die im Xcode-Schnittstellen-Generator zugewiesen werden kann. Um diese Aktion aufzurufen, wenn sich der Zustand ändert, verwenden wir den folgenden Code:

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

Zunächst überprüfen wir, ob dem Steuerelement eine Aktion zugewiesen wurde. Als Nächstes rufen wir die Aktion auf, wenn sie definiert wurde.

Verwenden des benutzerdefinierten Steuerelements

Wenn unser benutzerdefiniertes Steuerelement vollständig definiert ist, können wir es entweder der Benutzeroberfläche unserer Xamarin.Mac-App mithilfe von C#-Code oder im Xcode-Schnittstellen-Generator hinzufügen.

Um das Steuerelement mithilfe des Schnittstellen-Generators hinzuzufügen, führen Sie zunächst einen sauber Build des Xamarin.Mac-Projekts durch, und doppelklicken Sie dann auf die Main.storyboard Datei, um sie im Schnittstellen-Generator zur Bearbeitung zu öffnen:

Bearbeiten des Storyboards in Xcode

Ziehen Sie als Nächstes einen Custom View in das Design der Benutzeroberfläche:

Auswählen einer benutzerdefinierten Ansicht aus der Bibliothek

Wenn die Benutzerdefinierte Ansicht weiterhin ausgewählt ist, wechseln Sie zum Identitätsinspektor , und ändern Sie die Klasse der Ansicht in NSFlipSwitch:

Festlegen der View-Klasse

Wechseln Sie zum Assistenten-Editor , und erstellen Sie ein Outlet für das benutzerdefinierte Steuerelement (stellen Sie sicher, dass es in der ViewController.h Datei und nicht in der .m Datei gebunden wird):

Konfigurieren einer neuen Steckdose

Speichern Sie Ihre Änderungen, kehren Sie zu Visual Studio für Mac zurück, und lassen Sie die Synchronisierung der Änderungen zu. Bearbeiten Sie die ViewController.cs Datei, und lassen Sie die ViewDidLoad Methode wie folgt aussehen:

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);
    };
}

Hier reagieren wir auf das ValueChanged oben für die NSFlipSwitch -Klasse definierte Ereignis und schreiben den aktuellen Wert aus, wenn der Benutzer auf das Steuerelement klickt.

Optional können wir zum Schnittstellen-Generator zurückkehren und eine Aktion für das Steuerelement definieren:

Konfigurieren einer neuen Aktion

Bearbeiten Sie die ViewController.cs Datei erneut, und fügen Sie die folgende Methode hinzu:

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

Wichtig

Sie sollten entweder das Ereignis verwenden oder eine Aktion im Schnittstellen-Generator definieren, aber Sie sollten nicht beide Methoden gleichzeitig verwenden, da sie in Konflikt stehen können.

Zusammenfassung

Dieser Artikel hat sich ausführlich mit dem Erstellen eines wiederverwendbaren benutzerdefinierten Benutzeroberflächensteuerelements in einer Xamarin.Mac-Anwendung befasst. Wir haben gesehen, wie sie die Benutzeroberfläche von benutzerdefinierten Steuerelementen zeichnen, die beiden Standard Möglichkeiten, auf Maus- und Benutzereingaben zu reagieren, und wie das neue Steuerelement für Aktionen im Xcode-Schnittstellen-Generator verfügbar gemacht wird.