Criando controles personalizados no Xamarin.Mac

Ao trabalhar com C# e .NET em um aplicativo Xamarin.Mac, você tem acesso aos mesmos Controles de Usuário que um desenvolvedor que trabalha no Objective-C, Swift e Xcode . Como o Xamarin.Mac se integra diretamente ao Xcode, você pode usar o Construtor de Interfaces do Xcode para criar e manter seus Controles de Usuário (ou, opcionalmente, criá-los diretamente no código C#).

Embora o macOS forneça uma grande quantidade de controles de usuário internos, pode haver momentos em que você precisa criar um controle personalizado para fornecer funcionalidades não fornecidas prontas para uso ou para corresponder a um tema de interface do usuário personalizado (como uma interface do jogo).

Exemplo de um controle de interface do usuário personalizado

Neste artigo, abordaremos os conceitos básicos da criação de um controle de interface do usuário personalizado reutilizável em um aplicativo Xamarin.Mac. É altamente sugerido que você trabalhe primeiro no artigo Olá, Mac , especificamente nas seções Introdução ao Xcode e Construtor de Interfaces e Saídas e Ações , pois aborda os principais conceitos e técnicas que usaremos neste artigo.

Talvez você queira dar uma olhada na seção Expondo classes/métodos C# do Objective-C documento Interno do Xamarin.Mac , ele explica os Register comandos e Export usados para conectar suas classes C# a Objective-C objetos e elementos de interface do usuário.

Introdução aos controles personalizados

Conforme indicado acima, pode haver momentos em que você precisa criar um Controle de Interface do Usuário Personalizado reutilizável para fornecer funcionalidade exclusiva para a interface do usuário do aplicativo Xamarin.Mac ou para criar um tema de interface do usuário personalizado (como uma interface do jogo).

Nessas situações, você pode herdar NSControl facilmente e criar uma ferramenta personalizada que pode ser adicionada à interface do usuário do aplicativo por meio do código C# ou por meio do Construtor de Interfaces do Xcode. Ao herdar do NSControl controle personalizado, você terá automaticamente todos os recursos padrão que um Controle de Interface do Usuário interno tem (como NSButton).

Se o controle de Interface do Usuário personalizado apenas exibir informações (como um gráfico personalizado e uma ferramenta gráfica), talvez você queira herdar de NSView em vez de NSControl.

Não importa qual classe base seja usada, as etapas básicas para criar um controle personalizado são as mesmas.

Neste artigo, criará um componente personalizado do Flip Switch que fornece um tema exclusivo da interface do usuário e um exemplo de criação de um controle de interface do usuário personalizado totalmente funcional.

Criando o controle personalizado

Como o controle personalizado que estamos criando responderá à entrada do usuário (cliques no botão esquerdo do mouse), herdaremos de NSControl. Dessa forma, nosso controle personalizado terá automaticamente todos os recursos padrão que um Controle de Interface do Usuário interno tem e responderá como um controle macOS padrão.

Em Visual Studio para Mac, abra o projeto Xamarin.Mac para o qual você deseja criar um Controle de Interface do Usuário Personalizado (ou crie um novo). Adicione uma nova classe e chame-a NSFlipSwitchde :

Adicionando uma nova classe

Em seguida, edite a NSFlipSwitch.cs classe e faça com que ela se pareça com o seguinte:

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

    }
}

A primeira coisa a observar sobre nossa classe personalizada em que estamos herdando NSControl e usando o comando Register para expor essa classe e Objective-C o Construtor de Interfaces do Xcode:

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

Nas seções a seguir, vamos dar uma olhada no restante do código acima em detalhes.

Acompanhando o estado do controle

Como nosso Controle Personalizado é um comutador, precisamos de uma maneira de controlar o estado Ativado/Desativado do comutador. Lidamos com isso com o seguinte código em NSFlipSwitch:

private bool _value = false;
...

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

Quando o estado do comutador é alterado, precisamos de uma maneira de atualizar a interface do usuário. Fazemos isso forçando o controle a redesenhar sua interface do usuário com NeedsDisplay = true.

Se nosso controle exigisse mais que um único estado Ativado/Desativado (por exemplo, um comutador de vários estados com três posições), poderíamos ter usado uma Enumeração para acompanhar o estado. Para nosso exemplo, um bool simples fará.

Também adicionamos um método auxiliar para trocar o estado da opção entre Ativado e Desativado:

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

Posteriormente, expandiremos essa classe auxiliar para informar o chamador quando o estado de alternância for alterado.

Desenhando a interface do controle

Vamos usar as rotinas de desenho gráfico principal para desenhar a Interface do Usuário do nosso controle personalizado em runtime. Antes de fazermos isso, precisamos ativar camadas para nosso controle. Fazemos isso com o seguinte método privado:

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

Esse método é chamado de cada um dos construtores do controle para garantir que o controle esteja configurado corretamente. Por exemplo:

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

Em seguida, precisamos substituir o DrawRect método e adicionar as rotinas do Core Graphic para desenhar o controle:

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

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

}

Vamos ajustar a representação visual para o controle quando o estado dele for alterado (como ir de Ativado para Desativado). Sempre que o estado for alterado, podemos usar o NeedsDisplay = true comando para forçar o controle a redesenhar para o novo estado.

Respondendo à entrada do usuário

Há duas maneiras básicas de adicionar a entrada do usuário ao nosso controle personalizado: Substituir Rotinas de Tratamento de Mouse ou Reconhecedores de Gestos. Qual método usamos será baseado na funcionalidade exigida pelo nosso controle.

Importante

Para qualquer controle personalizado criado, você deve usar Métodos de SubstituiçãoouReconhecedores de Gestos, mas não ambos ao mesmo tempo em que eles podem entrar em conflito uns com os outros.

Manipulando a entrada do usuário com métodos de substituição

Objetos que herdam de (ou NSView) têm vários métodos de substituição para lidar com a entrada de NSControl mouse ou teclado. Para nosso controle de exemplo, queremos inverter o estado da opção entre Ativado e Desativado quando o usuário clicar no controle com o botão esquerdo do mouse. Podemos adicionar os seguintes métodos de substituição à NSFlipSwitch classe para lidar com isso:

#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

No código acima, chamamos o FlipSwitchState método (definido acima) para inverter o estado Ativado/Desativado da opção no MouseDown método . Isso também forçará o controle a ser redesenhado para refletir o estado atual.

Manipulando a entrada do usuário com reconhecedores de gestos

Opcionalmente, você pode usar Reconhecedores de Gestos para lidar com o usuário que interage com o controle. Remova as substituições adicionadas acima, edite o Initialize método e faça com que ele se pareça com o seguinte:

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

Aqui, estamos criando um novo NSClickGestureRecognizer e chamando nosso FlipSwitchState método para alterar o estado do comutador quando o usuário clica nele com o botão esquerdo do mouse. O AddGestureRecognizer (click) método adiciona o Reconhecimento de Gestos ao controle .

Novamente, qual método usamos depende do que estamos tentando realizar com nosso controle personalizado. Se precisarmos de acesso de baixo nível à interação do usuário, use os Métodos de Substituição. Se precisarmos de funcionalidade predefinida, como cliques do mouse, use Reconhecedores de Gestos.

Respondendo a eventos de alteração de estado

Quando o usuário altera o estado do nosso controle personalizado, precisamos de uma maneira de responder à alteração de estado no código (como fazer algo quando clica em um botão personalizado).

Para fornecer essa funcionalidade, edite a NSFlipSwitch classe e adicione o seguinte código:

#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

Em seguida, edite o FlipSwitchState método e faça com que ele se pareça com o seguinte:

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

Primeiro, fornecemos um ValueChanged evento ao qual podemos adicionar um manipulador no código C# para que possamos executar uma ação quando o usuário alterar o estado da opção.

Segundo, como nosso controle personalizado herda de NSControl, ele tem automaticamente uma Ação que pode ser atribuída no Construtor de Interfaces do Xcode. Para chamar essa Ação quando o estado for alterado, usamos o seguinte código:

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

Primeiro, marcar para ver se uma Ação foi atribuída ao controle. Em seguida, chamaremos a Ação se ela tiver sido definida.

Usando o controle personalizado

Com nosso controle personalizado totalmente definido, podemos adicioná-lo à interface do usuário do aplicativo Xamarin.Mac usando o código C# ou no Construtor de Interfaces do Xcode.

Para adicionar o controle usando o Construtor de Interfaces, primeiro faça um limpo build do projeto Xamarin.Mac e clique duas vezes no Main.storyboard arquivo para abri-lo no Construtor de Interfaces para edição:

Editando o storyboard no Xcode

Em seguida, arraste um Custom View para o design da Interface do Usuário:

Selecionando uma exibição personalizada na biblioteca

Com a Exibição Personalizada ainda selecionada, alterne para o Inspetor de Identidade e altere a Classe do modo de exibição para NSFlipSwitch:

Definindo a classe de Exibição

Alterne para o Editor Assistente e crie uma Tomada para o controle personalizado (certificando-se de associá-lo no ViewController.h arquivo e não no .m arquivo):

Configurando uma nova tomada

Salve suas alterações, retorne ao Visual Studio para Mac e permita que as alterações sejam sincronizadas. Edite o ViewController.cs arquivo e faça com que o ViewDidLoad método se pareça com o seguinte:

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

Aqui, respondemos ao ValueChanged evento que definimos acima na NSFlipSwitch classe e gravamos o Valor atual quando o usuário clica no controle.

Opcionalmente, podemos retornar ao Construtor de Interfaces e definir uma Ação no controle:

Configurando uma nova ação

Novamente, edite o ViewController.cs arquivo e adicione o seguinte método:

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

Importante

Você deve usar o Evento ou definir uma Ação no Construtor de Interfaces, mas não deve usar ambos os métodos ao mesmo tempo ou eles podem entrar em conflito uns com os outros.

Resumo

Este artigo deu uma olhada detalhada na criação de um controle de interface do usuário personalizado reutilizável em um aplicativo Xamarin.Mac. Vimos como desenhar a interface do usuário de controles personalizados, as duas main maneiras de responder à entrada do mouse e do usuário e como expor o novo controle a Ações no Construtor de Interfaces do Xcode.