Menús en Xamarin.Mac

En este artículo se explica cómo trabajar con menús en una aplicación de Xamarin.Mac. Describe cómo crear y mantener menús y elementos de menú en Xcode e Interface Builder y cómo trabajar con ellos mediante programación.

Al trabajar con C# y .NET en una aplicación de Xamarin.Mac, tiene acceso a los mismos menús de Cocoa que un desarrollador que trabaja en Objective-C y Xcode. Dado que Xamarin.Mac se integra directamente con Xcode, puede utilizar Interface Builder de Xcode para crear y mantener las barras de menú, los menús y los elementos de menú (u opcionalmente crearlos directamente en código C#).

Los menús son una parte integral de la experiencia de usuario de una aplicación Mac y suelen aparecer en varias partes de la interfaz de usuario:

  • La barra de menús de la aplicación: este es el menú principal que aparece en la parte superior de la pantalla para cada aplicación Mac.
  • Menús contextuales: aparecen cuando el usuario hace clic con el botón derecho o con el botón control en un elemento de una ventana.
  • La barra de estado : es el área situada en el extremo derecho de la barra de menús de la aplicación, que aparece en la parte superior de la pantalla (a la izquierda del reloj de la barra de menús) y crece hacia la izquierda a medida que se le agreguen elementos.
  • Menú Acoplar: el menú de cada aplicación en el acople que aparece cuando el usuario hace clic con el botón derecho o con el control en el icono de la aplicación, o cuando el usuario hace clic con el botón izquierdo en el icono y mantiene pulsado el botón del mouse.
  • Botón emergente y listas desplegables: un botón emergente muestra un elemento seleccionado y presenta una lista de opciones entre las que seleccionar al hacer clic el usuario. Una lista desplegable es un tipo de botón emergente que suele utilizarse para seleccionar comandos específicos en el contexto de la tarea actual. Ambos pueden aparecer en cualquier parte de una ventana.

Un menú de ejemplo

En este artículo, trataremos los aspectos básicos del trabajo con barras de menú, menús y elementos de menú de Cocoa en una aplicación de Xamarin.Mac. Se recomienda encarecidamente trabajar primero en el artículo Hello, Mac, específicamente en las secciones Introducción a Xcode e Interface Builder y Salidas y acciones, ya que trata conceptos clave y técnicas que usaremos en este artículo.

Es posible que quiera echar un vistazo a la sección Exponer clases o métodos de C# a Objective-C del documento Xamarin.Mac Internals, también explica los atributos Register y Export que se usan para conectar las clases de C# a Objective-C objetos y elementos de la interfaz de usuario.

Barra de menús de la aplicación

A diferencia de las aplicaciones que se ejecutan en el sistema operativo Windows, donde cada ventana puede tener su propia barra de menús adjunta, todas las aplicaciones que se ejecutan en macOS tienen una única barra de menús que recorre la parte superior de la pantalla y que se utiliza para todas las ventanas de esa aplicación:

Una barra de menús

Los elementos de esta barra de menús se activan o desactivan en función del contexto o estado actual de la aplicación y su interfaz de usuario en cualquier momento dado. Por ejemplo: si el usuario selecciona un campo de texto, se habilitarán elementos del menú Edición como Copiar y Cortar.

De acuerdo con Apple y de forma predeterminada, todas las aplicaciones macOS tienen un conjunto estándar de menús y elementos de menú que aparecen en la barra de menús de la aplicación:

  • Menú Apple: este menú proporciona acceso a elementos de todo el sistema que están disponibles para el usuario en todo momento, independientemente de la aplicación que se esté ejecutando. El desarrollador no puede modificar estos elementos.
  • Menú Aplicación: este menú muestra el nombre de la aplicación en negrita y ayuda al usuario a identificar qué aplicación se está ejecutando actualmente. Contiene elementos que se aplican a la aplicación en su conjunto y no a un documento o proceso determinado, como salir de la aplicación.
  • Menú Archivo: elementos usados para crear, abrir o guardar documentos con los que funciona la aplicación. Si la aplicación no está basada en documentos, este menú se puede cambiar o quitar.
  • Menú Editar: contiene comandos como Cortar, Copiar y Pegar que se usan para editar o modificar elementos en la interfaz de usuario de la aplicación.
  • Menú Formato: si la aplicación funciona con texto, este menú contiene comandos para ajustar el formato de ese texto.
  • Menú Vista: contiene comandos que afectan a cómo se muestra el contenido (visto) en la interfaz de usuario de la aplicación.
  • Menús específicos de la aplicación: estos son los menús específicos de la aplicación (como un menú de marcadores para un explorador web). Deben aparecer entre los menús Vista y Ventana de la barra.
  • Menú Ventana: contiene comandos para trabajar con las ventanas de su aplicación, así como una lista de las ventanas abiertas actualmente.
  • Menú Ayuda: si la aplicación proporciona ayuda en pantalla, el menú Ayuda debe ser el más a la derecha de la barra.

Para obtener más información sobre la barra de menús de las aplicaciones y los menús y elementos de menú estándar, consulte las Directrices de interfaz humana de Apple.

Barra de menús de la aplicación predeterminada

Siempre que cree un nuevo proyecto de Xamarin.Mac, obtendrá automáticamente una barra de menús de aplicación estándar predeterminada que tiene los elementos típicos que normalmente tendría una aplicación de macOS (como se ha comentado en la sección anterior). La barra de menús predeterminada de su aplicación se define en el archivo Main.storyboard (junto con el resto de la interfaz de usuario de su aplicación) bajo el proyecto en el Panel de solución:

Selección del guion gráfico principal

Haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en el Interface Builder de Xcode y aparecerá la interfaz del editor de menús:

Edición de la interfaz de usuario en Xcode, en la que se muestra el Main.storyboard.

Desde aquí podemos hacer clic en elementos como la opción Abrir del menú Archivo y editar o ajustar sus propiedades en el Inspector de atributos:

Edición de los atributos de un menú

Más adelante hablaremos de cómo agregar, editar y eliminar menús y elementos. Por ahora solo queremos ver qué menús y elementos de menú están disponibles de forma predeterminada y cómo se han expuesto automáticamente al código a través de un conjunto de salidas y acciones predefinidas (para obtener más información, consulte nuestra documentación de salidas y acciones).

Por ejemplo, si hacemos clic en el Inspector de conexiones del elemento de menú Abrir, veremos que se conecta automáticamente a la acción openDocument::

Visualización de la acción adjunta

Si selecciona la Primer respondedor en la Jerarquía de interfaces y se desplaza hacia abajo en el inspector de conexiones, verá la definición de la acción openDocument: a la que está asociado el elemento de menú Abrir (junto con otras acciones predeterminadas de la aplicación que están y no están conectadas automáticamente a controles):

Visualización de todas las acciones adjuntas

¿Por qué esto es importante? En la siguiente sección verá cómo funcionan estas acciones definidas automáticamente con otros elementos de la interfaz de usuario de Cocoa para habilitar y deshabilitar automáticamente los elementos de menú, así como proporcionar funcionalidad integrada para los elementos.

Más adelante usaremos estas acciones integradas para habilitar y deshabilitar elementos del código y proporcionaremos nuestra propia funcionalidad cuando se seleccionen.

Funcionalidad de menú integrada

Si ejecuta una aplicación de Xamarin.Mac recién creada antes de agregar ningún elemento o código de interfaz de usuario, observará que algunos elementos se conectan y habilitan automáticamente (con todas las funciones integradas automáticamente), como el elemento Salir del menú Aplicación:

Un elemento de menú habilitado

Mientras que otros elementos de menú, como Cortar, Copiar y Pegar no lo son:

Elementos de menú deshabilitados

Detengamos la aplicación y hagamos doble clic en el archivo Main.storyboard en el Panel de solución para abrirlo y editarlo en Interface Builder de Xcode. A continuación, arrastre una Vista de texto de la Biblioteca al controlador de vista de la ventana en el Editor de interfaz:

Selección de una vista de texto en la biblioteca

En el Editor de restricciones, anclemos la vista de texto a los bordes de la ventana y establézcala para que crezca y se encoja con la ventana haciendo clic en las cuatro vigas I rojas de la parte superior del editor y pulsando el botón Agregar 4 restricciones:

Edición de las restricciones

Guarde los cambios en el diseño de la interfaz de usuario y vuelva a activar Visual Studio para Mac para sincronizar los cambios con el proyecto de Xamarin.Mac. Ahora inicie la aplicación, escriba un texto en la vista de texto, selecciónelo y abra el menú Edición:

Los elementos de menú se habilitan o deshabilitan automáticamente

Observe cómo los elementos Cortar, Copiar y Pegar están habilitados automáticamente y totalmente funcionales, todo sin escribir una sola línea de código.

¿Qué ocurre aquí? Recuerde las acciones predefinidas integradas que vienen conectadas a los elementos del menú predeterminado (como se ha presentado anteriormente), la mayoría de los elementos de la interfaz de usuario Cocoa que forman parte de macOS tienen enlaces integrados a acciones específicas (como copy:). De este modo, cuando se agregan a una ventana, se activan y se seleccionan, el elemento o elementos de menú correspondientes asociados a esa acción se habilitan automáticamente. Si el usuario selecciona ese elemento del menú, se llama a la funcionalidad integrada en el elemento de la interfaz de usuario y se ejecuta, todo ello sin intervención del desarrollador.

Habilitar y deshabilitar menús y elementos

De forma predeterminada, cada vez que se produce un evento de usuario, NSMenu habilita y deshabilita automáticamente cada menú y elemento de menú visible en función del contexto de la aplicación. Hay tres formas de habilitar o deshabilitar un elemento:

  • Habilitación automática de menús: un elemento de menú se habilita si NSMenu puede encontrar un objeto apropiado que responda a la acción a la que está conectado el elemento. Por ejemplo, la vista de texto anterior que tenía un enlace integrado a la acción copy:.
  • Acciones personalizadas y validateMenuItem: Para cualquier elemento de menú que esté enlazado a una ventana o a una acción personalizada del controlador de vista, puede agregar la acción validateMenuItem: y habilitar o deshabilitar manualmente los elementos de menú.
  • Habilitación manual de menús: se configura manualmente la propiedad Enabled de cada NSMenuItem para habilitar o deshabilitar individualmente cada elemento de un menú.

Para elegir un sistema, establezca la propiedad AutoEnablesItems de un NSMenu. true es automático (el comportamiento predeterminado) y false es manual.

Importante

Si decide usar la habilitación del menú manual, ninguno de los elementos de menú, incluso aquellos controlados por clases de AppKit como NSTextView, se actualizan automáticamente. Usted será responsable de habilitar y deshabilitar todos los elementos a mano en código.

Uso de validateMenuItem

Como se indicó anteriormente, para cualquier elemento de menú enlazado a un ventana o vista acción personalizada del controlador de vistas, puede agregar la acción validateMenuItem: y habilitar o deshabilitar manualmente los elementos de menú.

En el siguiente ejemplo, la propiedad Tag se utilizará para decidir el tipo de elemento de menú que se habilitará o deshabilitará mediante la acción validateMenuItem: en función del estado del texto seleccionado en un elemento de menú NSTextView. La propiedad Tag se ha establecido en Interface Builder para cada elemento de menú:

Establecimiento de la propiedad Tag

Y el siguiente código agregado al controlador de vista:

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

Cuando se ejecuta este código y no se selecciona ningún texto en NSTextView, los dos elementos de menú de encapsulado se deshabilitan (aunque estén conectados a acciones en el controlador de vista):

Mostrar elementos deshabilitados

Si se selecciona una sección de texto y se vuelve a abrir el menú, estarán disponibles los dos elementos del menú de encapsulado:

Mostrar elementos habilitados

Habilitación y respuesta a elementos de menú en el código

Como hemos visto anteriormente, con solo agregar elementos de interfaz de usuario Cocoa específicos a nuestro diseño de UI (como un campo de texto), varios de los elementos de menú predeterminados se habilitarán y funcionarán automáticamente, sin tener que escribir ningún código. A continuación, vamos a agregar nuestro propio código de C# a nuestro proyecto de Xamarin.Mac para habilitar un elemento de menú y proporcionar funcionalidad cuando el usuario lo seleccione.

Por ejemplo, digamos que queremos que el usuario pueda utilizar el elemento Abrir del menú Archivo para seleccionar una carpeta. Dado que queremos que esta función se aplique a toda la aplicación y no se limite a una ventana o elemento de interfaz de usuario concreto, vamos a agregar el código necesario a nuestro delegado de aplicación.

Haga doble clic en el archivo AppDelegate.CS en el Panel de solución para abrirlo para su edición:

Selección del delegado de aplicación

Agregue el código siguiente al 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 a ejecutar la aplicación ahora y abrir el menú Archivo:

Menú Archivo

Observe que el elemento de menú Abrir ahora está habilitado. Si lo seleccionamos, se mostrará el cuadro de diálogo abierto:

Un cuadro de diálogo abierto

Si hacemos clic en el botón Abrir, se mostrará el mensaje de alerta:

Un mensaje de cuadro de diálogo de ejemplo

La línea clave aquí era [Export ("openDocument:")], indica NSMenu que nuestro AppDelegate tiene un método void OpenDialog (NSObject sender) que responde a la acción openDocument:. Si lo recuerda, el elemento de menú Abrir se conecta automáticamente a esta acción de forma predeterminada en Interface Builder:

Visualización de las acciones adjuntas

A continuación veremos cómo crear nuestro propio menú, elementos de menú y acciones y cómo responder a ellos en código.

Trabajar con el menú reciente abierto

De forma predeterminada, el menú Archivo contiene un elemento Abrir reciente que mantiene un registro de los últimos archivos que el usuario ha abierto con su aplicación. Si va a crear una aplicación de Xamarin.Mac basada en NSDocument, este menú se controlará automáticamente. Para cualquier otro tipo de aplicación de Xamarin.Mac, será responsable de administrar y responder manualmente a este elemento de menú.

Para manejar manualmente el menú Abrir recientes, primero tendrá que informarle de que se ha abierto o guardado un nuevo archivo mediante lo siguiente:

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

Aunque su aplicación no utilice NSDocuments, sigue utilizando el NSDocumentController para mantener el menú Abrir recientes enviando un NSUrl con la ubicación del archivo al método NoteNewRecentDocumentURL del SharedDocumentController.

A continuación, debe sobrescribir el método OpenFile del delegado de la aplicación para abrir cualquier archivo que el usuario seleccione en el menú Abrir reciente. Por ejemplo:

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

Devuelve true si el archivo se puede abrir, en caso contrario devuelve false y se mostrará un aviso integrado al usuario de que el archivo no se ha podido abrir.

Debido a que el nombre de archivo y la ruta devueltos por el menú Abrir reciente, pueden incluir un espacio, necesitamos escapar correctamente este carácter antes de crear un NSUrl o obtendremos un error. Lo hacemos con el siguiente código:

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

Por último, creamos un NSUrl que apunta al archivo y usamos un método auxiliar en el delegado de la aplicación para abrir una nueva ventana y cargar el archivo en él:

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

Para reunir todo, echemos un vistazo a una implementación de ejemplo en un archivo 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
    }
}

En función de los requisitos de su aplicación, es posible que no desee que el usuario abra el mismo archivo en más de una ventana al mismo tiempo. En nuestra aplicación de ejemplo, si el usuario elige un archivo que ya está abierto (ya sea desde las opciones de menú Abrir reciente o Abrir..), la ventana que contiene el archivo pasa a primer plano.

Para ello, utilizamos el siguiente código en nuestro método de ayuda:

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

Hemos diseñado nuestra clase ViewController para contener la ruta de acceso al archivo en su propiedad Path. A continuación, recorremos en bucle todas las ventanas abiertas actualmente en la aplicación. Si el archivo ya está abierto en una de las ventanas, se lleva al frente de todas las demás ventanas con:

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

Si no se encuentra ninguna coincidencia, se abre una nueva ventana con el archivo cargado y se anota el archivo en el menú Abrir reciente:

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

Trabajar con acciones de ventana personalizadas

Al igual que las acciones integradas de Primer respondedor que vienen preconectadas a elementos de menú estándar, puede crear nuevas acciones personalizadas y conectarlas a elementos de menú en Interface Builder.

En primer lugar, define una acción personalizada en uno de los controladores de ventana de tu aplicación. Por ejemplo:

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

A continuación, haga doble clic en el archivo del guión gráfico de la aplicación en el Panel de solución para abrirlo y editarlo en Interface Builder de Xcode. Seleccione el Primer respondedor en la Escena de aplicación, luego cambie al Inspector de atributos:

El inspector de atributos

Haga clic en el botón + situado en la parte inferior del Inspector de atributos para agregar una nueva acción personalizada:

Adición de una nueva acción

Asígnele el mismo nombre que la acción personalizada que creó en el controlador de ventana:

Edición del nombre de la acción

Haga clic con el botón Control y arrástrelo desde un elemento de menú hasta el Primer respondedor en la Escena de aplicación. En la lista emergente, seleccione la nueva acción que acaba de crear (defineKeyword: en este ejemplo):

Adjuntar una acción

Guarde los cambios en el guión gráfico y vuelva a Visual Studio para Mac para sincronizar los cambios. Si ejecuta la aplicación, el elemento de menú al que haya conectado la acción personalizada se habilitará o deshabilitará automáticamente (en función de la ventana con la acción abierta) y al seleccionar el elemento de menú se ejecutará la acción:

Prueba de la nueva acción

Agregar, editar y eliminar menús

Como hemos visto en las secciones anteriores, una aplicación de Xamarin.Mac viene con un número preestablecido de menús y elementos de menú predeterminados que los controles de interfaz de usuario específicos activarán y a los que responderán automáticamente. También hemos visto cómo agregar código a nuestra aplicación que también habilitará y responderá a estos elementos predeterminados.

En esta sección veremos cómo eliminar elementos de menú que no necesitamos, reorganizar menús y agregar nuevos menús, elementos de menú y acciones.

Haga doble clic en el archivo Main.storyboard en el Panel de solución para abrirlo y editarlo:

Haga doble clic en el archivo de guion gráfico para editar la interfaz de usuario en Xcode.

Para nuestra aplicación específica de Xamarin.Mac, no vamos a usar el menú predeterminado Vista, por lo que vamos a quitarlo. En Jerarquía de interfaz seleccione el elemento de menú Vista que forma parte de la barra de menús principal:

Selección del elemento de menú Ver

Presione eliminar o retroceso para eliminar el menú. A continuación, no vamos a usar todos los elementos del menú Formato y queremos mover los elementos que vamos a usar en los menús secundarios. En jerarquía de interfaz seleccione los siguientes elementos de menú:

Resaltado de varios elementos

Arrastre los elementos del Menú principal desde el submenú en el que se encuentran actualmente:

Arrastrar elementos de menú al menú primario

El menú debería ser similar al siguiente:

Elementos de la nueva ubicación

A continuación, arrastremos el submenú Texto fuera del menú Formato y coloquémoslo en la barra de menú principal, entre los menús Formato y Ventana:

El menú Texto

Volvamos al menú Formato y borremos el elemento de submenú Fuente. A continuación, seleccione el menú Formato y cámbiele el nombre "Fuente":

El menú Fuente

A continuación, vamos a crear un menú personalizado de frases predefinidas que se anexarán automáticamente al texto en la vista de texto cuando se seleccionen. En el cuadro de búsqueda de la parte inferior del Inspector de bibliotecas, escriba "menú". Esto facilitará la búsqueda y el trabajo con todos los elementos de la interfaz de usuario del menú:

El inspector de biblioteca

Ahora vamos a hacer lo siguiente para crear nuestro menú:

  1. Arrastre un elemento de Menú desde el Inspector de biblioteca a la barra de menús entre los menús Texto y Ventana:

    Selección de un nuevo elemento de menú en la biblioteca

  2. Cambie el nombre del elemento "Frases":

    Establecimiento del nombre del menú

  3. A continuación, arrastre un Menú desde el Inspector de biblioteca:

    Selección de un menú en la biblioteca

  4. Arrastre entonces Menú sobre el nuevo Elemento de menú que acabamos de crear y cámbiele el nombre a "Frases":

    Edición del nombre del menú

  5. Ahora vamos a cambiar el nombre de los tres Elementos de menú de forma predeterminada "Dirección", "Fecha" y "Saludo":

    El menú Frases

  6. Vamos a agregar un cuarto Elemento de menú arrastrando un Elemento de menú desde el Inspector de biblioteca y llamándolo "Firma":

    Edición del nombre del elemento de menú

  7. Guarde los cambios en la barra de menús.

Ahora vamos a crear un conjunto de acciones personalizadas para que los nuevos elementos de menú se expongan al código de C#. En Xcode cambiemos a la vista Asistente:

Creación de las acciones necesarias

Hagamos lo siguiente:

  1. Arrastre el control desde el elemento de menú Dirección al archivo AppDelegate.h.

  2. Cambie el tipo de Conexión a Acción:

    Selección del tipo de acción

  3. Escriba un Nombre de "phraseAddress" y presione el botón Conectar para crear la nueva acción:

    Para configurar la acción, escriba un nombre.

  4. Repita los pasos anteriores para los elementos de menú Fecha, Saludo y Firma:

    Acciones completadas

  5. Guarde los cambios en la barra de menús.

A continuación tenemos que crear una salida para nuestra vista de texto para que podamos ajustar su contenido desde el código. Seleccione el archivo ViewController.h en el Editor de asistente y cree una nueva salida llamada documentText:

Creación de una salida

Vuelva a Visual Studio para Mac para sincronizar los cambios de Xcode. A continuación, edite el archivo ViewController.cs y haga que tenga el siguiente aspecto:

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

Esto expone el texto de nuestra vista de texto fuera de la clase ViewController e informa al delegado de la aplicación cuando la ventana obtiene o pierde el foco. Ahora edite el archivo AppDelegate.cs y haga que tenga el siguiente aspecto:

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

Aquí hemos hecho el AppDelegate una clase parcial para que podamos usar las acciones y salidas que definimos en el Interface Builder. También exponemos un textEditor para supervisar qué ventana está actualmente en foco.

Los siguientes métodos se utilizan para manejar nuestro menú personalizado y elementos de menú:

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

Ahora, si ejecutamos nuestra aplicación, todos los elementos del menú Frase estarán activos y agregarán la frase a la vista de texto cuando se seleccione:

Ejemplo de la aplicación en ejecución

Ahora que ya sabemos cómo trabajar con la barra de menús de la aplicación, vamos a crear un menú contextual personalizado.

Creación de menús a partir de código

Además de crear menús y elementos de menú con Interface Builder de Xcode, puede haber ocasiones en las que una aplicación de Xamarin.Mac necesite crear, modificar o eliminar un menú, un submenú o un elemento de menú desde el código.

En el siguiente ejemplo, se crea una clase para contener la información sobre los elementos de menú y los submenú que se crearán dinámicamente sobre la marcha:

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

Agregar menús y elementos

Con esta clase definida, la siguiente rutina analizará una colección de objetosLanguageFormatCommand y creará de forma recursiva nuevos menús y elementos de menú anexándolos a la parte inferior del menú existente (creado en el Interface Builder) que se ha pasado:

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 cualquier objeto LanguageFormatCommand que tenga una propiedad Title en blanco, esta rutina crea un elemento de menú Separador (una delgada línea gris) entre las secciones del menú:

menuItem = NSMenuItem.SeparatorItem;

Si se proporciona un título, se crea un nuevo elemento de menú con ese título:

menuItem = new NSMenuItem (command.Title);

Si el objeto LanguageFormatCommand contiene objetos secundarios LanguageFormatCommand, se crea un submenú y se llama al método AssembleMenu de forma recursiva para crear ese menú:

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

Para cualquier nuevo elemento de menú que no tenga submenús, el código se agrega para controlar el elemento de menú seleccionado por el usuario:

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

Prueba de la creación del menú

Con todo el código anterior en su lugar, si se creó la siguiente colección de objetos LanguageFormatCommand:

// 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)"));

Y pasada esa colección a la función AssembleMenu (con el menú Formato como base), se crearían los siguientes menús dinámicos y elementos de menú:

Los nuevos elementos de menú de la aplicación en ejecución

Quitar menús y elementos

Si necesita quitar cualquier menú o elemento de menú de la interfaz de usuario de la aplicación, puede utilizar el método RemoveItemAt de la clase NSMenu simplemente dándole el índice basado en cero del elemento a quitar.

Por ejemplo, para quitar los menús y los elementos de menú creados por la rutina anterior, puede usar el siguiente código:

public void UnpopulateFormattingMenu(NSMenu menu) {

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

En el caso del código anterior, los cuatro primeros elementos del menú se crean en Interface Builder de Xcode y están disponibles en la aplicación, por lo que no se quitan dinámicamente.

Menús contextuales

Los menús contextuales aparecen cuando el usuario hace clic con el botón derecho del ratón o con el botón Control en un elemento de una ventana. De forma predeterminada, varios de los elementos de la interfaz de usuario integrados en macOS ya tienen menús contextuales adjuntos (como la vista de texto). Sin embargo, puede haber ocasiones en las que queramos crear nuestros propios menús contextuales personalizados para un elemento de interfaz de usuario que hayamos agregado a una ventana.

Vamos a editar nuestro archivo Main.storyboard en Xcode y agregar una ventana Ventana a nuestro diseño, establecer su Clase a "NSPanel" en el Inspector de identidad, agregar un nuevo elemento Asistente al menú Ventana, y adjuntarlo a la nueva ventana utilizando un Show Segue:

Establezca el tipo de segue en el archivo Main.storyboard.

Hagamos lo siguiente:

  1. Arrastre una Etiqueta del Inspector de bibliotecas a la ventana Panel y defina su texto como "Propiedad":

    Edición del valor de la etiqueta

  2. A continuación, arrastre un Menú desde el Inspector de bibliotecas hasta el Controlador de vistas en la Jerarquía de Vistas y cambie el nombre de los tres elementos de menú predeterminados Documento, Texto y Fuente:

    Los elementos de menú necesarios

  3. Ahora arrastre desde la Etiqueta de propiedad al Menú:

    Arrastrar para crear una transición

  4. En el cuadro de diálogo emergente, seleccione Menú:

    Para establecer el tipo de segue, seleccione el menú Salidas en el menú contextual Etiqueta.

  5. Desde el Inspector de identidad, establezca la clase del controlador de vista a "PanelViewController":

    Establecimiento de la clase de segue

  6. Vuelva a Visual Studio para Mac para sincronizar y, a continuación, vuelva a Interface Builder.

  7. Cambie al Editor de asistente y seleccione el archivo PanelViewController.h.

  8. Cree una acción para el elemento de menú Documento llamada propertyDocument:

    Configure la acción denominada propertyDocument.

  9. Repita las acciones de creación para los elementos de menú restantes:

    Repita las acciones para los elementos de menú restantes.

  10. Por último, cree una salida para la Etiqueta de propiedad llamada propertyLabel:

    Configuración de la salida

  11. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

Edite el archivo PanelViewController.cs y agregue el siguiente 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";
}

Ahora, si ejecutamos la aplicación y hacemos clic con el botón derecho en la etiqueta de la propiedad en el panel, veremos nuestro menú contextual personalizado. Si seleccionamos y elemento en el menú, el valor de la etiqueta cambiará:

Menú contextual en ejecución

A continuación veremos cómo crear menús en la barra de estado.

Menús de la barra de estado

Los menús de la barra de estado muestran una colección de elementos de menú de estado que proporcionan interacción o información al usuario, como un menú o una imagen que refleja el estado de una aplicación. El menú de la barra de estado de una aplicación está habilitado y activo incluso si la aplicación se ejecuta en segundo plano. La barra de estado de todo el sistema se encuentra a la derecha de la barra de menús de las aplicaciones y es la única barra de estado disponible actualmente en macOS.

Vamos a editar nuestro archivo AppDelegate.cs y hacer que el método DidFinishLaunching tenga el siguiente aspecto:

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 da acceso a la barra de estado de todo el sistema. var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable); crea un nuevo elemento de barra de estado. A partir de ahí creamos un menú y una serie de elementos de menú y adjuntamos el menú al elemento de la barra de estado que acabamos de crear.

Si ejecutamos la aplicación, se mostrará el nuevo elemento de la barra de estado. Al seleccionar un elemento en el menú se cambiará el texto de la vista de texto:

Menú de la barra de estado en ejecución

A continuación, echemos un vistazo a la creación de elementos de menú de acoplamiento personalizados.

Menús de acoplamiento personalizados

El menú de acoplamiento aparece para su aplicación Mac cuando el usuario hace clic con el botón derecho o con el botón de control en el icono de la aplicación en el acoplamiento:

Un menú de acoplamiento personalizado

Vamos a crear un menú de acoplamiento personalizado para nuestra aplicación haciendo lo siguiente:

  1. En Visual Studio para Mac, haga clic con el botón derecho en el proyecto de la aplicación y seleccione Agregar>Nuevo archivo... En el cuadro de diálogo de nuevo archivo, seleccione Xamarin.Mac>Definición de interfaz vacía, use "DockMenu" para el Nombre y haga clic en el botón Nuevo para crear el nuevo archivo DockMenu.xib:

    Adición de una definición de interfaz vacía

  2. En el Panel de solución, haga doble clic en el archivo DockMenu.xib para abrirlo para su edición en Xcode. Cree un nuevo Menú con los siguientes elementos: Dirección, Fecha, Saludo y Firma

    Diseño de la interfaz de usuario

  3. A continuación, conectemos nuestros nuevos elementos de menú a las acciones existentes que creamos para nuestro menú personalizado en la sección anterior Agregar, editar y eliminar menús. Cambie al Inspector de conexiones y seleccione la Primer respondedor en la Jerarquía de interfaces. Desplácese hacia abajo y busque la acción phraseAddress:. Arrastre una línea desde el círculo de esa acción hasta el elemento de menú Dirección:

    Arrastrar una línea al elemento de menú Dirección.

  4. Repita el proceso para todos los demás elementos del menú, asociándolos a sus acciones correspondientes:

    Repita para otros elementos de menú que los adjuntan a sus acciones correspondientes.

  5. A continuación, seleccione la Aplicación en la Jerarquía de interfaces. En el Inspector de conexión, arrastre una línea desde el círculo de la salida dockMenu hasta el menú que acabamos de crear:

    Arrastrar el cable hacia arriba de la salida

  6. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizar con Xcode.

  7. Haga doble clic en el archivo Info.plist para abrirlo para editarlo:

    Edición del archivo Info.plist

  8. Haz clic en la pestaña Fuente, en la parte inferior de la pantalla:

    Selección de la vista Origen

  9. Haga clic en Agregar nueva entrada, haga clic en el botón más verde, establezca el nombre de la propiedad en "AppleDockMenu" y el valor en "DockMenu" (el nombre de nuestro nuevo archivo .xib sin la extensión):

    Adición del elemento DockMenu

Ahora, si ejecutamos nuestra aplicación y hacemos clic con el botón derecho sobre su icono en el Acoplamiento, aparecerán nuestros nuevos elementos de menú:

Ejemplo del menú dock en ejecución

Si seleccionamos uno de los elementos personalizados en el menú, se modificará el texto de la vista de texto.

Botón emergente y listas desplegables

Un botón emergente muestra un elemento seleccionado y presenta una lista de opciones entre las que elegir cuando el usuario hace clic en él. Una lista desplegable es un tipo de botón emergente que suele utilizarse para seleccionar comandos específicos en el contexto de la tarea actual. Ambos pueden aparecer en cualquier parte de una ventana.

Vamos a crear un botón emergente personalizado para nuestra aplicación haciendo lo siguiente:

  1. Edita el archivo Main.storyboard en Xcode y arrastra un Botón emergente desde el Inspector de bibliotecas a la ventana Panel que creamos en la sección Menús contextuales:

    Adición de un botón emergente

  2. Agregue un nuevo elemento de menú y establezca los títulos de los elementos del elemento emergente en: Dirección, Fecha, Saludo y Firma

    Configuración de los elementos de menú

  3. A continuación, vamos a conectar nuestros nuevos elementos de menú a las acciones existentes que creamos para nuestro menú personalizado en la sección Agregar, editar y eliminar menús anterior. Cambie al Inspector de conexiones y seleccione la Primer respondedor en la Jerarquía de interfaces. Desplácese hacia abajo y busque la acción phraseAddress:. Arrastre una línea desde el círculo de esa acción hasta el elemento de menú Dirección:

    Arrastrar para conectar una acción

  4. Repita el proceso para todos los demás elementos del menú, asociándolos a sus acciones correspondientes:

    Todas las acciones necesarias

  5. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizar con Xcode.

Ahora, si ejecutamos nuestra aplicación y seleccionamos un elemento de la ventana emergente, el texto de nuestra vista de texto cambiará:

Ejemplo de la ventana emergente en ejecución

Puede crear listas desplegables y trabajar con ellas exactamente igual que con los botones emergentes. En lugar de adjuntar una acción existente, puede crear sus propias acciones personalizadas, como hicimos para nuestro menú contextual en la sección Menús contextuales.

Resumen

En este artículo se ha realizado un vistazo detallado al trabajo con menús y elementos de menú en una aplicación de Xamarin.Mac. Primero examinamos la barra de menús de la aplicación, luego la creación de menús contextuales, a continuación los menús de la barra de estado y los menús personalizados de acoplamiento. Por último, tratamos los menús emergentes y las listas desplegables.