Menus no Xamarin.Mac

Este artigo aborda o trabalho com menus em um aplicativo Xamarin.Mac. Ele descreve a criação e manutenção de menus e itens de menu no Xcode e no Interface Builder e como trabalhar com eles programaticamente.

Ao trabalhar com C# e .NET em um aplicativo Xamarin.Mac, você tem acesso aos mesmos menus do Cocoa que um desenvolvedor trabalhando no Objective-C Xcode faz. Como o Xamarin.Mac se integra diretamente ao Xcode, você pode usar o Construtor de Interfaces do Xcode para criar e manter suas barras de menu, menus e itens de menu (ou, opcionalmente, criá-los diretamente no código C#).

Os menus são parte integrante da experiência do usuário de um aplicativo Mac e geralmente aparecem em várias partes da interface do usuário:

  • A barra de menus do aplicativo - Este é o menu principal que aparece na parte superior da tela para cada aplicativo Mac.
  • Menus contextuais - Aparecem quando o usuário clica com o botão direito do mouse ou clica com o botão direito do mouse em um item em uma janela.
  • A barra de status - Esta é a área no lado direito da barra de menus do aplicativo que aparece na parte superior da tela (à esquerda do relógio da barra de menus) e cresce para a esquerda à medida que os itens são adicionados a ela.
  • Menu do Dock - O menu de cada aplicativo no dock que aparece quando o usuário clica com o botão direito do mouse ou controla o ícone do aplicativo, ou quando o usuário clica com o botão esquerdo do mouse no ícone e mantém pressionado o botão do mouse.
  • Botão pop-up e listas suspensas - Um botão pop-up exibe um item selecionado e apresenta uma lista de opções para selecionar quando clicado pelo usuário. Uma lista suspensa é um tipo de botão pop-up geralmente usado para selecionar comandos específicos para o contexto da tarefa atual. Ambos podem aparecer em qualquer lugar de uma janela.

Um menu de exemplo

Neste artigo, abordaremos os conceitos básicos de como trabalhar com barras de menu, menus e itens de menu do Cocoa em um aplicativo Xamarin.Mac. É altamente recomendável que você trabalhe primeiro no artigo Olá, Mac, especificamente nas seções Introdução ao Xcode e ao Construtor de Interface e Saídas e Ações, pois ele aborda os principais conceitos e técnicas que usaremos neste artigo.

Você pode querer dar uma olhada na seção Expondo classes C# / métodos para Objective-Cdo documento Xamarin.Mac Internals também, ele explica os Register atributos e Export usados para conectar suas classes C# a Objective-C objetos e elementos da interface do usuário.

A barra de menus do aplicativo

Ao contrário dos aplicativos executados no sistema operacional Windows, onde cada janela pode ter sua própria barra de menus anexada a ela, cada aplicativo executado no macOS tem uma única barra de menus que é executada ao longo da parte superior da tela que é usada para cada janela nesse aplicativo:

Uma barra de menus

Os itens nesta barra de menus são ativados ou desativados com base no contexto ou estado atual do aplicativo e sua interface de usuário a qualquer momento. Por exemplo: se o usuário selecionar um campo de texto, os itens no menu Editar serão habilitados, como Copiar e Recortar.

De acordo com a Apple e por padrão, todos os aplicativos do macOS têm um conjunto padrão de menus e itens de menu que aparecem na barra de menus do aplicativo:

  • Menu Apple - Este menu fornece acesso a itens de todo o sistema que estão disponíveis para o usuário em todos os momentos, independentemente do aplicativo que está sendo executado. Esses itens não podem ser modificados pelo desenvolvedor.
  • Menu do aplicativo - Este menu exibe o nome do aplicativo em negrito e ajuda o usuário a identificar qual aplicativo está sendo executado no momento. Ele contém itens que se aplicam ao aplicativo como um todo e não a um determinado documento ou processo, como sair do aplicativo.
  • Menu Arquivo - Itens usados para criar, abrir ou salvar documentos com os quais seu aplicativo trabalha. Se o aplicativo não for baseado em documento, esse menu poderá ser renomeado ou removido.
  • Menu Editar - Mantém comandos como Recortar, Copiar e Colar , que são usados para editar ou modificar elementos na interface do usuário do aplicativo.
  • Menu Formatar - Se o aplicativo funcionar com texto, esse menu contém comandos para ajustar a formatação desse texto.
  • Menu Exibir - Mantém comandos que afetam como o conteúdo é exibido (visualizado) na interface do usuário do aplicativo.
  • Menus específicos do aplicativo - São todos os menus específicos do seu aplicativo (como um menu de favoritos para um navegador da Web). Eles devem aparecer entre os menus Exibir e Janela na barra.
  • Menu Janela - Contém comandos para trabalhar com janelas em seu aplicativo, bem como uma lista de janelas abertas atuais.
  • Menu Ajuda - Se o aplicativo fornecer ajuda na tela, o menu Ajuda deverá ser o menu mais à direita na barra.

Para obter mais informações sobre a barra de menus do aplicativo e menus e itens de menu padrão, consulte as Diretrizes de interface humana da Apple.

A barra de menus padrão do aplicativo

Sempre que você cria um novo projeto Xamarin.Mac, você obtém automaticamente uma barra de menus de aplicativo padrão padrão que tem os itens típicos que um aplicativo macOS normalmente teria (conforme discutido na seção acima). A barra de menus padrão do aplicativo é definida no arquivo Main.storyboard (junto com o restante da interface do usuário do aplicativo) no projeto no Painel de Soluções:

Selecione o storyboard principal

Clique duas vezes no arquivo Main.storyboard para abri-lo para edição no Construtor de Interfaces do Xcode e você verá a interface do editor de menus:

Editando a interface do usuário no Xcode, mostrando o storyboard de pontos principal.

A partir daqui, podemos clicar em itens como o item de menu Abrir no menu Arquivo e editar ou ajustar suas propriedades no Inspetor de Atributos:

Editando os atributos de um menu

Começaremos a adicionar, editar e excluir menus e itens mais adiante neste artigo. Por enquanto, queremos apenas ver quais menus e itens de menu estão disponíveis por padrão e como eles foram automaticamente expostos ao código por meio de um conjunto de saídas e ações predefinidas (para obter mais informações, consulte nossa documentação de Saídas e Ações ).

Por exemplo, se clicarmos no Inspetor de Conexão para o item de menu Abrir, podemos ver que ele está automaticamente conectado à openDocument: ação:

Exibindo a ação anexada

Se você selecionar o Primeiro Respondente na Hierarquia de Interface e rolar para baixo no Inspetor de Conexão, verá a definição da ação à qual o item de menu Abrir está anexado openDocument: (junto com várias outras ações padrão para o aplicativo que estão e não estão conectadas automaticamente aos controles):

Exibindo todas as ações anexadas

Por que isso é importante? Na próxima seção, veremos como essas ações definidas automaticamente funcionam com outros elementos da interface do usuário do Cocoa para habilitar e desabilitar automaticamente os itens de menu, bem como, fornecer funcionalidade interna para os itens.

Mais tarde, usaremos essas ações internas para habilitar e desabilitar itens do código e fornecer nossa própria funcionalidade quando eles forem selecionados.

Funcionalidade de menu integrada

Se você executou um aplicativo Xamarin.Mac recém-criado antes de adicionar qualquer item ou código da interface do usuário, notará que alguns itens são automaticamente conectados e habilitados para você (com funcionalidade totalmente integrada automaticamente), como o item Sair no menu Aplicativo:

Um item de menu habilitado

Enquanto outros itens de menu, como Recortar, Copiar e Colar não são:

Itens de menu desativados

Vamos parar o aplicativo e clicar duas vezes no arquivo Main.storyboard no Solution Pad para abri-lo para edição no Construtor de Interface do Xcode. Em seguida, arraste uma Vista de Texto da Biblioteca para o controlador de vista da janela no Editor de Interfaces:

Selecionando um Modo de Exibição de Texto na Biblioteca

No Editor de restrições, vamos fixar a exibição de texto nas bordas da janela e defini-la onde ela cresce e diminui com a janela, clicando em todos os quatro feixes de I vermelhos na parte superior do editor e clicando no botão Adicionar 4 restrições:

Editando os contraints

Salve suas alterações no design da interface do usuário e alterne o Visual Studio para Mac para sincronizar as alterações com seu projeto Xamarin.Mac. Agora inicie o aplicativo, digite algum texto na exibição de texto, selecione-o e abra o menu Editar :

Os itens de menu são ativados/desativados automaticamente

Observe como os itens Recortar, Copiar e Colar são ativados automaticamente e totalmente funcionais, tudo sem escrever uma única linha de código.

O que está acontecendo? Lembre-se das ações predefinidas internas que vêm conectadas aos itens de menu padrão (como apresentado acima), a maioria dos elementos da interface do usuário do Cocoa que fazem parte do macOS tem ganchos integrados para ações específicas (como copy:). Assim, quando eles são adicionados a uma janela, ativos e selecionados, o item de menu correspondente ou itens anexados a essa ação são ativados automaticamente. Se o usuário selecionar esse item de menu, a funcionalidade incorporada ao elemento da interface do usuário será chamada e executada, tudo sem intervenção do desenvolvedor.

Habilitando e desabilitando menus e itens

Por padrão, sempre que ocorre um evento de usuário, NSMenu habilita e desabilita automaticamente cada menu e item de menu visível com base no contexto do aplicativo. Há três maneiras de habilitar/desabilitar um item:

  • Ativação automática de menu - Um item de menu é habilitado se NSMenu puder localizar um objeto apropriado que responda à ação à qual o item está conectado. Por exemplo, a exibição de texto acima que tinha um gancho interno para a copy: ação.
  • Ações personalizadas e validateMenuItem: - Para qualquer item de menu vinculado a uma ação personalizada do controlador de janela ou exibição, você pode adicionar a ação e habilitar ou desabilitar manualmente os itens de validateMenuItem: menu.
  • Habilitação manual do menu - Você define manualmente a Enabled propriedade de cada NSMenuItem um para habilitar ou desabilitar cada item em um menu individualmente.

Para escolher um sistema, defina a AutoEnablesItems propriedade de um NSMenuarquivo . true é automático (o comportamento padrão) e false é manual.

Importante

Se você optar por usar a habilitação manual de menu, nenhum dos itens de menu, mesmo aqueles controlados por classes AppKit como NSTextView, serão atualizados automaticamente. Você será responsável por habilitar e desabilitar todos os itens manualmente no código.

Usando validateMenuItem

Como dito acima, para qualquer item de menu vinculado a uma Ação Personalizada do Controlador de Janela ou de Exibição, você pode adicionar a ação e habilitar ou desabilitar manualmente os validateMenuItem: itens de menu.

No exemplo a seguir, a Tag propriedade será usada para decidir o tipo de item de menu que será habilitado/desabilitado validateMenuItem: pela ação com base no estado do texto selecionado em um NSTextViewarquivo . A Tag propriedade foi definida no Interface Builder para cada item de menu:

Definindo a propriedade Tag

E o seguinte código adicionado ao controlador de exibição:

[Action("validateMenuItem:")]
public bool ValidateMenuItem (NSMenuItem item) {

    // Take action based on the menu item type
    // (As specified in its Tag)
    switch (item.Tag) {
    case 1:
        // Wrap menu items should only be available if
        // a range of text is selected
        return (TextEditor.SelectedRange.Length > 0);
    case 2:
        // Quote menu items should only be available if
        // a range is NOT selected.
        return (TextEditor.SelectedRange.Length == 0);
    }

    return true;
}

Quando esse código é executado e nenhum texto é selecionado no NSTextView, os dois itens de menu de quebra automática são desabilitados (mesmo que estejam conectados a ações no controlador de exibição):

Mostrando itens desabilitados

Se uma seção de texto for selecionada e o menu reaberto, os dois itens de menu de quebra automática estarão disponíveis:

Mostrando itens habilitados

Habilitando e respondendo a itens de menu no código

Como vimos acima, apenas adicionando elementos específicos da interface do usuário do Cocoa ao nosso design de interface do usuário (como um campo de texto), vários dos itens de menu padrão serão ativados e funcionarão automaticamente, sem ter que escrever nenhum código. Em seguida, vamos analisar como adicionar nosso próprio código C# ao nosso projeto Xamarin.Mac para habilitar um item de menu e fornecer funcionalidade quando o usuário selecioná-lo.

Por exemplo, digamos que queremos que o usuário possa usar o item Abrir no menu Arquivo para selecionar uma pasta. Como queremos que essa seja uma função de todo o aplicativo e não limitada a uma janela ou elemento de interface do usuário, vamos adicionar o código para lidar com isso ao nosso delegado de aplicativo.

No Solution Pad, clique duas vezes no AppDelegate.CS arquivo para abri-lo para edição:

Selecionando o representante do aplicativo

Adicione o seguinte código ao método DidFinishLaunching:

[Export ("openDocument:")]
void OpenDialog (NSObject sender)
{
    var dlg = NSOpenPanel.OpenPanel;
    dlg.CanChooseFiles = false;
    dlg.CanChooseDirectories = true;

    if (dlg.RunModal () == 1) {
        var alert = new NSAlert () {
            AlertStyle = NSAlertStyle.Informational,
            InformativeText = "At this point we should do something with the folder that the user just selected in the Open File Dialog box...",
            MessageText = "Folder Selected"
        };
        alert.RunModal ();
    }
}

Vamos executar o aplicativo agora e abrir o menu Arquivo :

O menu Arquivo

Observe que o item de menu Abrir agora está habilitado. Se selecioná-lo, a caixa de diálogo aberta será exibida:

Uma caixa de diálogo aberta

Se clicarmos no botão Abrir , nossa mensagem de alerta será exibida:

Uma mensagem de diálogo de exemplo

A linha chave aqui foi [Export ("openDocument:")], ele diz NSMenu que nosso AppDelegate tem um método void OpenDialog (NSObject sender) que responde à openDocument: ação. Se você se lembrar de cima, o item de menu Abrir é automaticamente conectado a essa ação por padrão no Construtor de Interfaces:

Exibindo as ações anexadas

Em seguida, vamos analisar a criação de nosso próprio menu, itens de menu e ações e responder a eles em código.

Trabalhando com o menu recente aberto

Por padrão, o menu Arquivo contém um item Abrir recente que mantém o controle dos últimos vários arquivos que o usuário abriu com seu aplicativo. Se você estiver criando um NSDocument aplicativo Xamarin.Mac baseado, este menu será manipulado para você automaticamente. Para qualquer outro tipo de aplicativo Xamarin.Mac, você será responsável por gerenciar e responder a este item de menu manualmente.

Para manipular manualmente o menu Abrir recente , primeiro você precisará informá-lo de que um novo arquivo foi aberto ou salvo usando o seguinte:

// Add document to the Open Recent menu
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

Mesmo que seu aplicativo não esteja usando NSDocumentso , você ainda usa o NSDocumentController para manter o menu Abrir recente enviando um NSUrl com o local do arquivo para o NoteNewRecentDocumentURL método do SharedDocumentController.

Em seguida, você precisa substituir o OpenFile método do delegado do aplicativo para abrir qualquer arquivo selecionado pelo usuário no menu Abrir recente . Por exemplo:

public override bool OpenFile (NSApplication sender, string filename)
{
    // Trap all errors
    try {
        filename = filename.Replace (" ", "%20");
        var url = new NSUrl ("file://"+filename);
        return OpenFile(url);
    } catch {
        return false;
    }
}

Retorne true se o arquivo puder ser aberto, caso contrário, retorne false e um aviso interno será exibido ao usuário de que o arquivo não pôde ser aberto.

Como o nome do arquivo e o caminho retornados do menu Abrir recente podem incluir um espaço, precisamos escapar corretamente desse caractere antes de criar um NSUrl ou obteremos um erro. Fazemos isso com o seguinte código:

filename = filename.Replace (" ", "%20");

Finalmente, criamos um NSUrl que aponta para o arquivo e usamos um método auxiliar no delegado do aplicativo para abrir uma nova janela e carregar o arquivo nele:

var url = new NSUrl ("file://"+filename);
return OpenFile(url);

Para reunir tudo, vamos dar uma olhada em um exemplo de implementação em um arquivo AppDelegate.cs :

using AppKit;
using Foundation;
using System.IO;
using System;

namespace MacHyperlink
{
    [Register ("AppDelegate")]
    public class AppDelegate : NSApplicationDelegate
    {
        #region Computed Properties
        public int NewWindowNumber { get; set;} = -1;
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void DidFinishLaunching (NSNotification notification)
        {
            // Insert code here to initialize your application
        }

        public override void WillTerminate (NSNotification notification)
        {
            // Insert code here to tear down your application
        }

        public override bool OpenFile (NSApplication sender, string filename)
        {
            // Trap all errors
            try {
                filename = filename.Replace (" ", "%20");
                var url = new NSUrl ("file://"+filename);
                return OpenFile(url);
            } catch {
                return false;
            }
        }
        #endregion

        #region Private Methods
        private bool OpenFile(NSUrl url) {
            var good = false;

            // Trap all errors
            try {
                var path = url.Path;

                // Is the file already open?
                for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
                    var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
                    if (content != null && path == content.FilePath) {
                        // Bring window to front
                        NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);
                        return true;
                    }
                }

                // Get new window
                var storyboard = NSStoryboard.FromName ("Main", null);
                var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

                // Display
                controller.ShowWindow(this);

                // Load the text into the window
                var viewController = controller.Window.ContentViewController as ViewController;
                viewController.Text = File.ReadAllText(path);
                viewController.SetLanguageFromPath(path);
                viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
                viewController.View.Window.RepresentedUrl = url;

                // Add document to the Open Recent menu
                NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

                // Make as successful
                good = true;
            } catch {
                // Mark as bad file on error
                good = false;
            }

            // Return results
            return good;
        }
        #endregion

        #region actions
        [Export ("openDocument:")]
        void OpenDialog (NSObject sender)
        {
            var dlg = NSOpenPanel.OpenPanel;
            dlg.CanChooseFiles = true;
            dlg.CanChooseDirectories = false;

            if (dlg.RunModal () == 1) {
                // Nab the first file
                var url = dlg.Urls [0];

                if (url != null) {
                    // Open the document in a new window
                    OpenFile (url);
                }
            }
        }
        #endregion
    }
}

Com base nos requisitos do seu aplicativo, talvez você não queira que o usuário abra o mesmo arquivo em mais de uma janela ao mesmo tempo. Em nosso aplicativo de exemplo, se o usuário escolher um arquivo que já está aberto (nos itens de menu Abrir recente ou Abrir..), a janela que contém o arquivo será trazida para a frente.

Para fazer isso, usamos o seguinte código em nosso método auxiliar:

var path = url.Path;

// Is the file already open?
for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
    var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
    if (content != null && path == content.FilePath) {
        // Bring window to front
        NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);
        return true;
    }
}

Projetamos nossa ViewController classe para manter o caminho para o arquivo em sua Path propriedade. Em seguida, percorremos todas as janelas abertas atualmente no aplicativo. Se o arquivo já estiver aberto em uma das janelas, ele será levado para a frente de todas as outras janelas usando:

NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);

Se nenhuma correspondência for encontrada, uma nova janela será aberta com o arquivo carregado e o arquivo será anotado no menu Abrir recente :

// Get new window
var storyboard = NSStoryboard.FromName ("Main", null);
var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

// Display
controller.ShowWindow(this);

// Load the text into the window
var viewController = controller.Window.ContentViewController as ViewController;
viewController.Text = File.ReadAllText(path);
viewController.SetLanguageFromPath(path);
viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
viewController.View.Window.RepresentedUrl = url;

// Add document to the Open Recent menu
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

Trabalhando com ações de janela personalizadas

Assim como as ações internas do First Responder que vêm pré-conectadas a itens de menu padrão, você pode criar novas ações personalizadas e conectá-las a itens de menu no Construtor de Interfaces.

Primeiro, defina uma ação personalizada em um dos controladores de janela do seu aplicativo. Por exemplo:

[Action("defineKeyword:")]
public void defineKeyword (NSObject sender) {
    // Preform some action when the menu is selected
    Console.WriteLine ("Request to define keyword");
}

Em seguida, clique duas vezes no arquivo de storyboard do aplicativo no Solution Pad para abri-lo para edição no Construtor de Interfaces do Xcode. Selecione o Primeiro Respondente na Cena do Aplicativo e alterne para o Inspetor de Atributos:

O Inspetor de Atributos

+ Clique no botão na parte inferior do Inspetor de Atributos para adicionar uma nova ação personalizada:

Adicionando uma nova ação

Dê a ele o mesmo nome da ação personalizada que você criou no controlador de janela:

Editando o nome da ação

Clique com a tecla Control pressionada e arraste de um item de menu para o Primeiro Respondente na Cena do Aplicativo. Na lista pop-up, selecione a nova ação que você acabou de criar (defineKeyword: neste exemplo):

Anexando uma ação

Salve as alterações no storyboard e retorne ao Visual Studio para Mac para sincronizar as alterações. Se você executar o aplicativo, o item de menu ao qual você conectou a ação personalizada será automaticamente habilitado/desabilitado (com base na janela com a ação sendo aberta) e a seleção do item de menu disparará a ação:

Testando a nova ação

Adicionando, editando e excluindo menus

Como vimos nas seções anteriores, um aplicativo Xamarin.Mac vem com um número predefinido de menus padrão e itens de menu que controles específicos da interface do usuário ativarão e responderão automaticamente. Também vimos como adicionar código ao nosso aplicativo que também habilitará e responderá a esses itens padrão.

Nesta seção, veremos como remover itens de menu que não precisamos, reorganizar menus e adicionar novos menus, itens de menu e ações.

Clique duas vezes no arquivo Main.storyboard no Solution Pad para abri-lo para edição:

Clique duas vezes no arquivo de storyboard para editar a interface do usuário no Xcode.

Para o nosso aplicativo Xamarin.Mac específico, não vamos usar o menu de visualização padrão, então vamos removê-lo. Na hierarquia de interface, selecione o item de menu Exibir que faz parte da barra de menus principal:

Selecionando o item de menu Exibir

Pressione delete ou backspace para excluir o menu. Em seguida, não usaremos todos os itens no menu Formatar e queremos mover os itens que usaremos para fora dos submenus. Na hierarquia de interface, selecione os seguintes itens de menu:

Realçando vários itens

Arraste os itens sob o menu pai do submenu onde eles estão atualmente:

Arrastando itens de menu para o menu pai

Seu menu agora deve ter a seguinte aparência:

Os itens no novo local

Em seguida, vamos arrastar o submenu Texto para fora do menu Formatar e colocá-lo na barra de menus principal entre os menus Formatar e Janela:

O menu Texto

Vamos voltar no menu Formatar e excluir o item do submenu Fonte . Em seguida, selecione o menu Formatar e renomeie-o como "Fonte":

O menu Fonte

Em seguida, vamos criar um menu personalizado de frases predefinidas que serão automaticamente anexadas ao texto na exibição de texto quando forem selecionadas. Na caixa de pesquisa na parte inferior do Inspetor de biblioteca, digite "menu". Isso facilitará a localização e o trabalho com todos os elementos da interface do usuário do menu:

O Inspetor de Biblioteca

Agora vamos fazer o seguinte para criar nosso menu:

  1. Arraste um Item de Menu do Inspetor de Biblioteca para a barra de menus entre os menus Texto e Janela :

    Selecionando um novo item de menu na Biblioteca

  2. Renomeie o item "Frases":

    Definindo o nome do menu

  3. Em seguida, arraste um Menu do Inspetor de biblioteca:

    Selecionando um menu na Biblioteca

  4. Solte então Menu no novo Item de Menu que acabamos de criar e altere seu nome para "Frases":

    Editando o nome do menu

  5. Agora vamos renomear os três itens de menu padrão "Endereço", "Data" e "Saudação":

    O menu Frases

  6. Vamos adicionar um quarto Item de Menu arrastando um Item de Menu do Inspetor de Biblioteca e chamando-o de "Assinatura":

    Editando o nome do item de menu

  7. Salve as alterações na barra de menus.

Agora vamos criar um conjunto de ações personalizadas para que nossos novos itens de menu sejam expostos ao código C#. No Xcode, vamos alternar para o modo de exibição Assistente:

Criando as ações necessárias

Vamos fazer o seguinte:

  1. Arraste com a tecla Control pressionada do item de menu Endereço para o arquivo AppDelegate.h .

  2. Alterne o tipo de conexão para Ação:

    Selecionando o tipo de ação

  3. Digite um Nome de "phraseAddress" e pressione o botão Conectar para criar a nova ação:

    Configurar a ação inserindo um nome.

  4. Repita as etapas acima para os itens de menu Data, Saudação e Assinatura :

    As ações concluídas

  5. Salve as alterações na barra de menus.

Em seguida, precisamos criar uma saída para nossa exibição de texto para que possamos ajustar seu conteúdo a partir do código. Selecione o arquivo ViewController.h no Editor Assistente e crie uma nova saída chamada documentText:

Criando uma saída

Retorne ao Visual Studio para Mac para sincronizar as alterações do Xcode. Em seguida, edite o arquivo ViewController.cs e faça com que ele tenha a seguinte aparência:

using System;

using AppKit;
using Foundation;

namespace MacMenus
{
    public partial class ViewController : NSViewController
    {
        #region Application Access
        public static AppDelegate App {
            get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
        }
        #endregion

        #region Computed Properties
        public override NSObject RepresentedObject {
            get {
                return base.RepresentedObject;
            }
            set {
                base.RepresentedObject = value;
                // Update the view, if already loaded.
            }
        }

        public string Text {
            get { return documentText.Value; }
            set { documentText.Value = value; }
        }
        #endregion

        #region Constructors
        public ViewController (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Do any additional setup after loading the view.
        }

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

            App.textEditor = this;
        }

        public override void ViewWillDisappear ()
        {
            base.ViewDidDisappear ();

            App.textEditor = null;
        }
        #endregion
    }
}

Isso expõe o texto de nossa exibição de texto fora da ViewController classe e informa ao representante do aplicativo quando a janela ganha ou perde o foco. Agora edite o arquivo AppDelegate.cs e faça com que ele tenha a seguinte aparência:

using AppKit;
using Foundation;
using System;

namespace MacMenus
{
    [Register ("AppDelegate")]
    public partial class AppDelegate : NSApplicationDelegate
    {
        #region Computed Properties
        public ViewController textEditor { get; set;} = null;
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void DidFinishLaunching (NSNotification notification)
        {
            // Insert code here to initialize your application
        }

        public override void WillTerminate (NSNotification notification)
        {
            // Insert code here to tear down your application
        }
        #endregion

        #region Custom actions
        [Export ("openDocument:")]
        void OpenDialog (NSObject sender)
        {
            var dlg = NSOpenPanel.OpenPanel;
            dlg.CanChooseFiles = false;
            dlg.CanChooseDirectories = true;

            if (dlg.RunModal () == 1) {
                var alert = new NSAlert () {
                    AlertStyle = NSAlertStyle.Informational,
                    InformativeText = "At this point we should do something with the folder that the user just selected in the Open File Dialog box...",
                    MessageText = "Folder Selected"
                };
                alert.RunModal ();
            }
        }

        partial void phrasesAddress (Foundation.NSObject sender) {

            textEditor.Text += "Xamarin HQ\n394 Pacific Ave, 4th Floor\nSan Francisco CA 94111\n\n";
        }

        partial void phrasesDate (Foundation.NSObject sender) {

            textEditor.Text += DateTime.Now.ToString("D");
        }

        partial void phrasesGreeting (Foundation.NSObject sender) {

            textEditor.Text += "Dear Sirs,\n\n";
        }

        partial void phrasesSignature (Foundation.NSObject sender) {

            textEditor.Text += "Sincerely,\n\nKevin Mullins\nXamarin,Inc.\n";
        }
        #endregion
    }
}

Aqui fizemos a AppDelegate classe a partial para que possamos usar as ações e saídas que definimos no Interface Builder. Também expomos um textEditor para rastrear qual janela está atualmente em foco.

Os seguintes métodos são usados para lidar com nosso menu personalizado e itens de menu:

partial void phrasesAddress (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Xamarin HQ\n394 Pacific Ave, 4th Floor\nSan Francisco CA 94111\n\n";
}

partial void phrasesDate (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += DateTime.Now.ToString("D");
}

partial void phrasesGreeting (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Dear Sirs,\n\n";
}

partial void phrasesSignature (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Sincerely,\n\nKevin Mullins\nXamarin,Inc.\n";
}

Agora, se executarmos nosso aplicativo, todos os itens no menu Frase estarão ativos e adicionarão a frase à exibição de texto quando selecionados:

Um exemplo do aplicativo em execução

Agora que temos as noções básicas de como trabalhar com a barra de menus do aplicativo, vamos ver como criar um menu contextual personalizado.

Criando menus a partir do código

Além de criar menus e itens de menu com o Construtor de Interfaces do Xcode, pode haver momentos em que um aplicativo Xamarin.Mac precise criar, modificar ou remover um menu, submenu ou item de menu do código.

No exemplo a seguir, uma classe é criada para armazenar as informações sobre os itens de menu e submenus que serão criados dinamicamente em tempo real:

using System;
using System.Collections.Generic;
using Foundation;
using AppKit;

namespace AppKit.TextKit.Formatter
{
    public class LanguageFormatCommand : NSObject
    {
        #region Computed Properties
        public string Title { get; set; } = "";
        public string Prefix { get; set; } = "";
        public string Postfix { get; set; } = "";
        public List<LanguageFormatCommand> SubCommands { get; set; } = new List<LanguageFormatCommand>();
        #endregion

        #region Constructors
        public LanguageFormatCommand () {

        }

        public LanguageFormatCommand (string title)
        {
            // Initialize
            this.Title = title;
        }

        public LanguageFormatCommand (string title, string prefix)
        {
            // Initialize
            this.Title = title;
            this.Prefix = prefix;
        }

        public LanguageFormatCommand (string title, string prefix, string postfix)
        {
            // Initialize
            this.Title = title;
            this.Prefix = prefix;
            this.Postfix = postfix;
        }
        #endregion
    }
}

Adicionando menus e itens

Com essa classe definida, a seguinte rotina analisará uma coleção de objetos e criará recursivamente novos menus e itens de LanguageFormatCommandmenu anexando-os à parte inferior do menu existente (criado no Construtor de Interfaces) que foi passado:

private void AssembleMenu(NSMenu menu, List<LanguageFormatCommand> commands) {
    NSMenuItem menuItem;

    // Add any formatting commands to the Formatting menu
    foreach (LanguageFormatCommand command in commands) {
        // Add separator or item?
        if (command.Title == "") {
            menuItem = NSMenuItem.SeparatorItem;
        } else {
            menuItem = new NSMenuItem (command.Title);

            // Submenu?
            if (command.SubCommands.Count > 0) {
                // Yes, populate submenu
                menuItem.Submenu = new NSMenu (command.Title);
                AssembleMenu (menuItem.Submenu, command.SubCommands);
            } else {
                // No, add normal menu item
                menuItem.Activated += (sender, e) => {
                    // Apply the command on the selected text
                    TextEditor.PerformFormattingCommand (command);
                };
            }
        }
        menu.AddItem (menuItem);
    }
}

Para qualquer LanguageFormatCommand objeto que tenha uma propriedade em brancoTitle, essa rotina cria um item de menu Separador (uma linha cinza fina) entre as seções do menu:

menuItem = NSMenuItem.SeparatorItem;

Se um título for fornecido, um novo item de menu com esse título será criado:

menuItem = new NSMenuItem (command.Title);

Se o LanguageFormatCommand objeto contiver objetos filho LanguageFormatCommand , um submenu será criado e o AssembleMenu método será chamado recursivamente para criar esse menu:

menuItem.Submenu = new NSMenu (command.Title);
AssembleMenu (menuItem.Submenu, command.SubCommands);

Para qualquer novo item de menu que não tenha submenus, o código é adicionado para manipular o item de menu que está sendo selecionado pelo usuário:

menuItem.Activated += (sender, e) => {
    // Do something when the menu item is selected
    ...
};

Testando a criação do menu

Com todo o código acima no lugar, se a seguinte coleção de LanguageFormatCommand objetos foi criada:

// Define formatting commands
FormattingCommands.Add(new LanguageFormatCommand("Strong","**","**"));
FormattingCommands.Add(new LanguageFormatCommand("Emphasize","_","_"));
FormattingCommands.Add(new LanguageFormatCommand("Inline Code","`","`"));
FormattingCommands.Add(new LanguageFormatCommand("Code Block","```\n","\n```"));
FormattingCommands.Add(new LanguageFormatCommand("Comment","<!--","-->"));
FormattingCommands.Add (new LanguageFormatCommand ());
FormattingCommands.Add(new LanguageFormatCommand("Unordered List","* "));
FormattingCommands.Add(new LanguageFormatCommand("Ordered List","1. "));
FormattingCommands.Add(new LanguageFormatCommand("Block Quote","> "));
FormattingCommands.Add (new LanguageFormatCommand ());

var Headings = new LanguageFormatCommand ("Headings");
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 1","# "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 2","## "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 3","### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 4","#### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 5","##### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 6","###### "));
FormattingCommands.Add (Headings);

FormattingCommands.Add(new LanguageFormatCommand ());
FormattingCommands.Add(new LanguageFormatCommand("Link","[","]()"));
FormattingCommands.Add(new LanguageFormatCommand("Image","![](",")"));
FormattingCommands.Add(new LanguageFormatCommand("Image Link","[![](",")](LinkImageHere)"));

E essa coleção passada para a AssembleMenu função (com o Menu Formatar definido como base), os seguintes menus dinâmicos e itens de menu seriam criados:

Os novos itens de menu no aplicativo em execução

Removendo menus e itens

Se você precisar remover qualquer menu ou item de menu da interface do usuário do aplicativo, você pode usar o RemoveItemAt NSMenu método da classe simplesmente dando-lhe o índice baseado em zero do item a ser removido.

Por exemplo, para remover os menus e itens de menu criados pela rotina acima, você pode usar o seguinte código:

public void UnpopulateFormattingMenu(NSMenu menu) {

    // Remove any additional items
    for (int n = (int)menu.Count - 1; n > 4; --n) {
        menu.RemoveItemAt (n);
    }
}

No caso do código acima, os quatro primeiros itens de menu são criados no Construtor de Interfaces do Xcode e estão disponíveis no aplicativo, para que não sejam removidos dinamicamente.

Menus contextuais

Os menus contextuais aparecem quando o usuário clica com o botão direito do mouse ou controla um item em uma janela. Por padrão, vários dos elementos da interface do usuário incorporados ao macOS já têm menus contextuais anexados a eles (como a exibição de texto). No entanto, pode haver momentos em que queremos criar nossos próprios menus contextuais personalizados para um elemento da interface do usuário que adicionamos a uma janela.

Vamos editar nosso arquivo Main.storyboard no Xcode e adicionar uma janela Janela ao nosso design, definir sua Classe como "NSPanel" no Inspetor de Identidade, adicionar um novo item Assistente ao menu Janela e anexá-lo à nova janela usando um Mostrar Segue:

Definindo o tipo de segue no arquivo de storyboard de ponto principal.

Vamos fazer o seguinte:

  1. Arraste um Rótulo do Inspetor de Biblioteca para a janela Painel e defina seu texto como "Propriedade":

    Editando o valor do rótulo

  2. Em seguida, arraste um Menu do Inspetor de Biblioteca para o Controlador de Exibição na Hierarquia de Exibição e renomeie os três itens de menu padrão Documento, Texto e Fonte:

    Os itens de menu necessários

  3. Agora, arraste com a tecla Control pressionada do Rótulo da propriedade para o menu:

    Arrastando para criar um segue

  4. Na caixa de diálogo pop-up, selecione Menu:

    Defina o tipo de opção selecionando o menu de Saídas no menu de contexto Rótulo.

  5. No Inspetor de Identidade, defina a classe do View Controller como "PanelViewController":

    Definindo a classe segue

  6. Alterne de volta para o Visual Studio para Mac para sincronizar e, em seguida, retorne ao Construtor de Interfaces.

  7. Alterne para o Editor Assistente e selecione o arquivo PanelViewController.h .

  8. Crie uma ação para o item de menu Documento chamada propertyDocument:

    Configurando a ação denominada propertyDocument.

  9. Repita as ações de criação para os itens de menu restantes:

    Repetir ações para os itens de menu restantes.

  10. Finalmente, crie uma saída para o Rótulo de propriedade chamado propertyLabel:

    Configurando a tomada

  11. Salve suas alterações e retorne ao Visual Studio para Mac para sincronizar com o Xcode.

Edite o arquivo PanelViewController.cs e adicione o seguinte código:

partial void propertyDocument (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Document";
}

partial void propertyFont (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Font";
}

partial void propertyText (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Text";
}

Agora, se executarmos o aplicativo e clicarmos com o botão direito do mouse no rótulo da propriedade no painel, veremos nosso menu contextual personalizado. Se selecionarmos e item no menu, o valor do rótulo será alterado:

O menu contextual em execução

Em seguida, vamos examinar a criação de menus da barra de status.

Menus da barra de status

Os menus da barra de status exibem uma coleção de itens de menu de status que fornecem interação ou feedback ao usuário, como um menu ou uma imagem que reflete o estado de um aplicativo. O menu da barra de status de um aplicativo está habilitado e ativo mesmo se o aplicativo estiver sendo executado em segundo plano. A barra de status de todo o sistema reside no lado direito da barra de menus do aplicativo e é a única barra de status atualmente disponível no macOS.

Vamos editar nosso arquivo AppDelegate.cs e fazer com que o DidFinishLaunching método tenha a seguinte aparência:

public override void DidFinishLaunching (NSNotification notification)
{
    // Create a status bar menu
    NSStatusBar statusBar = NSStatusBar.SystemStatusBar;

    var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable);
    item.Title = "Text";
    item.HighlightMode = true;
    item.Menu = new NSMenu ("Text");

    var address = new NSMenuItem ("Address");
    address.Activated += (sender, e) => {
        PhraseAddress(address);
    };
    item.Menu.AddItem (address);

    var date = new NSMenuItem ("Date");
    date.Activated += (sender, e) => {
        PhraseDate(date);
    };
    item.Menu.AddItem (date);

    var greeting = new NSMenuItem ("Greeting");
    greeting.Activated += (sender, e) => {
        PhraseGreeting(greeting);
    };
    item.Menu.AddItem (greeting);

    var signature = new NSMenuItem ("Signature");
    signature.Activated += (sender, e) => {
        PhraseSignature(signature);
    };
    item.Menu.AddItem (signature);
}

NSStatusBar statusBar = NSStatusBar.SystemStatusBar; nos dá acesso à barra de status de todo o sistema. var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable); Cria um novo item da barra de status. A partir daí, criamos um menu e uma série de itens de menu e anexamos o menu ao item da barra de status que acabamos de criar.

Se executarmos o aplicativo, o novo item da barra de status será exibido. A seleção de um item no menu alterará o texto na visualização de texto:

O menu da barra de status em execução

Em seguida, vamos examinar a criação de itens de menu de doca personalizados.

Menus de doca personalizados

O menu dock aparece para o aplicativo Mac quando o usuário clica com o botão direito do mouse ou clica com o botão direito do mouse no ícone do aplicativo no dock:

Um menu de doca personalizado

Vamos criar um menu de doca personalizado para o nosso aplicativo fazendo o seguinte:

  1. No Visual Studio para Mac, clique com o botão direito do mouse no projeto do aplicativo e selecione Adicionar>novo arquivo... Na caixa de diálogo do novo arquivo, selecione Definição de interface vazia do Xamarin.Mac>, use "DockMenu" para o Nome e clique no botão Novo para criar o novo arquivo DockMenu.xib:

    Adicionando uma definição de interface vazia

  2. No Solution Pad, clique duas vezes no arquivo DockMenu.xib para abri-lo para edição no Xcode. Crie um novo menu com os seguintes itens: Endereço, Data, Saudação e Assinatura

    Criação do layout da interface do usuário

  3. Em seguida, vamos conectar nossos novos itens de menu às nossas ações existentes que criamos para nosso menu personalizado na seção Adicionando, Editando e Excluindo Menus acima. Alterne para o Inspetor de Conexão e selecione o Primeiro Respondente na Hierarquia de Interface. Role para baixo e encontre a phraseAddress: ação. Arraste uma linha do círculo nessa ação para o item de menu Endereço :

    Arrastando uma linha para o item de menu Endereço.

  4. Repita para todos os outros itens de menu anexando-os às suas ações correspondentes:

    Repetindo para outros itens de menu anexando-os às suas ações correspondentes.

  5. Em seguida, selecione o Aplicativo na Hierarquia de Interface. No Inspetor de Conexão, arraste uma linha do círculo na dockMenu tomada para o menu que acabamos de criar:

    Arrastando o fio até a saída

  6. Salve suas alterações e volte para o Visual Studio para Mac para sincronizar com o Xcode.

  7. Clique duas vezes no arquivo Info.plist para abri-lo para edição:

    Editar o arquivo Info.plist

  8. Clique na guia Origem na parte inferior da tela:

    Selecionando o modo de exibição Código-fonte

  9. Clique em Adicionar nova entrada, clique no botão verde de mais, defina o nome da propriedade como "AppleDockMenu" e o valor como "DockMenu" (o nome do nosso novo arquivo .xib sem a extensão):

    Adicionando o item DockMenu

Agora, se executarmos nosso aplicativo e clicarmos com o botão direito do mouse em seu ícone no Dock, nossos novos itens de menu serão exibidos:

Um exemplo do menu dock em execução

Se selecionarmos um dos itens personalizados no menu, o texto em nossa visualização de texto será modificado.

Botão pop-up e listas suspensas

Um botão pop-up exibe um item selecionado e apresenta uma lista de opções para selecionar quando clicado pelo usuário. Uma lista suspensa é um tipo de botão pop-up geralmente usado para selecionar comandos específicos para o contexto da tarefa atual. Ambos podem aparecer em qualquer lugar de uma janela.

Vamos criar um botão pop-up personalizado para o nosso aplicativo fazendo o seguinte:

  1. Edite o arquivo Main.storyboard no Xcode e arraste um botão pop-up do Inspetor de biblioteca para a janela Painel que criamos na seção Menus contextuais:

    Adicionando um botão pop-up

  2. Adicione um novo item de menu e defina os títulos dos Itens no Pop-up como: Endereço, Data, Saudação e Assinatura

    Configurando os itens de menu

  3. Em seguida, vamos conectar nossos novos itens de menu às ações existentes que criamos para nosso menu personalizado na seção Adicionando, Editando e Excluindo Menus acima. Alterne para o Inspetor de Conexão e selecione o Primeiro Respondente na Hierarquia de Interface. Role para baixo e encontre a phraseAddress: ação. Arraste uma linha do círculo nessa ação para o item de menu Endereço :

    Arrastando para conectar uma ação

  4. Repita para todos os outros itens de menu anexando-os às suas ações correspondentes:

    Todas as ações necessárias

  5. Salve suas alterações e volte para o Visual Studio para Mac para sincronizar com o Xcode.

Agora, se executarmos nosso aplicativo e selecionarmos um item no pop-up, o texto em nossa exibição de texto será alterado:

Um exemplo do pop-up em execução

Você pode criar e trabalhar com listas suspensas exatamente da mesma forma que os botões pop-up. Em vez de anexar à ação existente, você pode criar suas próprias ações personalizadas, assim como fizemos para nosso menu contextual na seção Menus contextuais .

Resumo

Este artigo deu uma olhada detalhada no trabalho com menus e itens de menu em um aplicativo Xamarin.Mac. Primeiro examinamos a barra de menus do aplicativo, depois analisamos a criação de menus contextuais, em seguida examinamos os menus da barra de status e os menus personalizados do dock. Finalmente, cobrimos menus pop-up e listas suspensas.