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 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 en los que trabaja Objective-C un desarrollador y Xcode. Dado que Xamarin.Mac se integra directamente con Xcode, puede usar el Generador de interfaces de Xcode para crear y mantener las barras de menú, los menús y los elementos de menú (o, opcionalmente, crearlos directamente en código de 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:

  • 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 hace clic con el botón derecho en un elemento de una ventana.
  • La barra de estado: este es el área situada en el lado 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 a la izquierda a medida que se agregan elementos.
  • Menú Acoplar: el menú de cada aplicación del dock que aparece cuando el usuario hace clic con el botón derecho o hace clic 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 presionado 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 normalmente se usa para seleccionar comandos específicos del contexto de la tarea actual. Ambos pueden aparecer en cualquier parte de una ventana.

An example menu

En este artículo, trataremos los conceptos básicos de trabajar con barras de menús de Cocoa, menús y elementos de menú 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 y Generador de interfaces 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# para Objective-C la sección del documento Xamarin.Mac Internals , también explica los Register atributos 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 asociada, cada aplicación que se ejecuta en macOS tiene una sola barra de menús que se ejecuta en la parte superior de la pantalla que se usa para cada ventana de esa aplicación:

A menu bar

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, los elementos del menú Editar aparecerán habilitados, 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ú de Apple: este menú proporciona acceso a los 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ú Ver: 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: se trata de menús específicos de la aplicación (como un menú de marcadores para un explorador web). Deben aparecer entre los menús Ver y Ventana de la barra.
  • Menú Ventana: contiene comandos para trabajar con ventanas en la aplicación, así como una lista de ventanas abiertas actuales.
  • Menú Ayuda: si la aplicación proporciona ayuda en pantalla, el menú Ayuda debe ser el menú más adecuado en la barra.

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

Barra de menús de la aplicación predeterminada

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

Select the main storyboard

Haga doble clic en el archivo Main.storyboard para abrirlo para editarlo en el Generador de interfaz de Xcode y se le presentará la interfaz del editor de menús:

Editing the UI in Xcode, showing the Main dot storyboard.

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

Editing a menu's attributes

Entraremos en agregar, editar y eliminar menús y elementos más adelante en este artículo. 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, vea nuestra documentación de salidas y acciones ).

Por ejemplo, si hacemos clic en el inspector de Conectar ion para el elemento de menú Abrir, podemos ver que se conecta automáticamente a la openDocument: acción:

Viewing the attached action

Si selecciona el primer respondedor en la jerarquía de interfaz y desplácese hacia abajo en el inspector de Conectar ion, y verá la definición de la openDocument: acción a la que se adjunta el elemento de menú Abrir (junto con otras acciones predeterminadas para la aplicación que están y no se conectan automáticamente a los controles):

Viewing all attached actions

¿Por qué esto es importante? En la sección siguiente 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 fuera la ejecución de una aplicación de Xamarin.Mac recién creada antes de agregar cualquier elemento o código de interfaz de usuario, observará que algunos elementos se conectan automáticamente y se habilitan automáticamente (con una funcionalidad completa integrada automáticamente), como el elemento Salir en el menú Aplicación :

An enabled menu item

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

Disabled menu items

Vamos a detener la aplicación y haga doble clic en el archivo Main.storyboard en el Panel de solución para abrirlo para editarlo en el Generador de interfaces de Xcode. A continuación, arrastre una vista de texto desde la biblioteca hasta el controlador de vista de la ventana en el Editor de interfaz:

Selecting a Text View from the Library

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

Editing the contraints

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

The menu items are automatically enabled/disabled

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 se conectan a los elementos de menú predeterminados (como se presenta anteriormente), la mayoría de los elementos de la interfaz de usuario cocoa que forman parte de macOS han integrado enlaces a acciones específicas (como copy:). Por lo tanto, cuando se agregan a una ventana, activa y seleccionada, el elemento de menú o los elementos correspondientes adjuntos a esa acción se habilitan automáticamente. Si el usuario selecciona ese elemento de menú, se llama a la funcionalidad integrada en el elemento de interfaz de usuario y se ejecuta, todo sin intervención del desarrollador.

Habilitación y deshabilitación de menús y elementos

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

  • Habilitación automática de menús: un elemento de menú está habilitado si NSMenu puede encontrar un objeto adecuado 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 copy: acción.
  • Acciones personalizadas y validateMenuItem: para cualquier elemento de menú enlazado a una acción personalizada de controlador de vista o ventana, puede agregar la validateMenuItem: acción y habilitar o deshabilitar manualmente elementos de menú.
  • Habilitación manual del menú: establece manualmente la Enabled propiedad de cada uno NSMenuItem para habilitar o deshabilitar cada elemento de un menú individualmente.

Para elegir un sistema, establezca la AutoEnablesItems propiedad de .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 los controlados por clases de AppKit como NSTextView, se actualizan automáticamente. Usted será responsable de habilitar y deshabilitar todos los elementos a mano en el código.

Uso de validateMenuItem

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

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

Setting the Tag property

Y el código siguiente 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 ajuste se deshabilitan (aunque estén conectados a acciones en el controlador de vista):

Showing disabled items

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

Showing enabled items

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

Como hemos visto anteriormente, simplemente agregando elementos específicos de la interfaz de usuario de Cocoa al diseño de la interfaz de usuario (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, veamos cómo 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 selecciona.

Por ejemplo, supongamos que queremos que el usuario pueda usar el elemento Abrir en el menú Archivo para seleccionar una carpeta. Dado que queremos que sea una función para toda la aplicación y no se limite a una ventana o elemento de interfaz de usuario, vamos a agregar el código para controlarlo a nuestro delegado de aplicación.

En el Panel de solución, haga doble clic en el AppDelegate.CS archivo para abrirlo para editarlo:

Selecting the app delegate

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 :

The File menu

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

An open dialog

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

An example dialog message

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 openDocument: acción. Si recuerdas desde arriba, el elemento de menú Abrir se conecta automáticamente a esta acción de forma predeterminada en el Generador de interfaces:

Viewing the attached actions

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

Trabajar con el menú reciente abierto

De forma predeterminada, el menú Archivo contiene un elemento Abrir reciente que realiza un seguimiento de los últimos archivos que el usuario ha abierto con la aplicación. Si va a crear una NSDocument aplicación de Xamarin.Mac basada, 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 controlar manualmente el menú Abrir reciente , primero deberá informarle de que se ha abierto o guardado un archivo nuevo mediante lo siguiente:

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

Aunque la aplicación no usa NSDocuments, sigue usando para NSDocumentController mantener el menú Abrir reciente enviando un NSUrl con la ubicación del archivo al NoteNewRecentDocumentURL método de SharedDocumentController.

A continuación, debe invalidar el OpenFile método del delegado de 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 se puede abrir el archivo; de lo contrario false , se mostrará una advertencia integrada al usuario que no se pudo abrir el archivo.

Dado que el nombre de archivo y la ruta de acceso devueltos desde el menú Abrir recientes pueden incluir un espacio, es necesario escapar correctamente de este carácter antes de crear o NSUrl se producirá un error. Lo hacemos con el código siguiente:

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

Según los requisitos de la aplicación, es posible que no quieras 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 los elementos de menú Abrir recientes o Open.), la ventana que contiene el archivo se lleva al principio.

Para ello, usamos el código siguiente en nuestro 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;
    }
}

Hemos diseñado nuestra ViewController clase para contener la ruta de acceso al archivo en su Path propiedad. A continuación, recorremos 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 el archivo se indica 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 del primer respondedor que vienen pre-cableadas a elementos de menú estándar, puede crear acciones nuevas, personalizadas y conectarlas a elementos de menú en el Generador de interfaces.

En primer lugar, defina una acción personalizada en uno de los controladores de ventana de la 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 storyboard de la aplicación en el Panel de solución para abrirlo para editarlo en el Generador de interfaces de Xcode. Seleccione el primer respondedor en la escena de la aplicación y, a continuación, cambie al Inspector de atributos:

The Attributes Inspector

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

Adding a new action

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

Editing the action name

Haga clic en el control y arrástrelo desde un elemento de menú al primer respondedor en la escena de la aplicación. En la lista emergente, seleccione la nueva acción que acaba de crear (defineKeyword: en este ejemplo):

Attaching an action

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 ha 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 activará la acción:

Testing the new action

Agregar, editar y eliminar menús

Como hemos visto en las secciones anteriores, una aplicación de Xamarin.Mac incluye un número preestablecido de menús predeterminados y elementos de menú a los que se activarán y responderán automáticamente los controles de interfaz de usuario específicos. 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 quitar 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 del Panel de solución para abrirlo para editarlo:

Double-clicking the storyboard file to edit the UI in Xcode.

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

Selecting the View menu item

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 submenúes. En La jerarquía de interfaz, seleccione los siguientes elementos de menú:

Highlighting multiple items

Arrastre los elementos debajo del menú primario desde el submenú donde están actualmente:

Dragging menu items to the parent menu

El menú debería ser similar al siguiente:

The items in the new location

A continuación, arrastremos el submenú Texto desde el menú Formato y colóquelo en la barra de menús principal entre los menús Formato y Ventana :

The Text menu

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

The Font menu

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

The Library Inspector

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 :

    Selecting a new menu item in the Library

  2. Cambie el nombre del elemento "Frases":

    Setting the menu name

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

    Selecting a menu from the Library

  4. Coloque el menú en el nuevo elemento de menú que acabamos de crear y cambie su nombre a "Frases":

    Editing the menu name

  5. Ahora vamos a cambiar el nombre de los tres elementos de menú predeterminados "Address", "Date" y "Greeting":

    The Phrases menu

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

    Editing the menu item name

  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 :

Creating the required actions

Hagamos lo siguiente:

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

  2. Cambie el tipo de Conectar ion a Acción:

    Selecting the action type

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

    Configuring the action by entering a name.

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

    The completed actions

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

A continuación, necesitamos crear una salida para nuestra vista de texto para que podamos ajustar su contenido a partir del código. Seleccione el archivo ViewController.h en el Editor del Asistente y cree una nueva salida denominada documentText:

Creating an outlet

Vuelva a Visual Studio para Mac para sincronizar los cambios de Xcode. A continuación, edite el archivo ViewController.cs y fíjelo como el siguiente:

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 ViewController clase e informa al delegado de la aplicación cuando la ventana obtiene o pierde el foco. Ahora edite el archivo AppDelegate.cs y fíjelo como el siguiente:

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 realizado una AppDelegate clase parcial para que podamos usar las acciones y salidas que definimos en el Generador de interfaces. También se expone un para realizar un textEditor seguimiento de qué ventana está actualmente en el foco.

Los métodos siguientes se usan para controlar nuestros elementos de menú y menú personalizados:

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:

An example of the app running

Ahora que tenemos los conceptos básicos de trabajar con la barra de menús de la aplicación hacia abajo, echemos un vistazo a la creación de 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 el Generador de interfaces de Xcode, puede haber ocasiones en las que una aplicación de Xamarin.Mac necesite crear, modificar o quitar un menú, submenú o elemento de menú del código.

En el ejemplo siguiente, se crea una clase para contener la información sobre los elementos de menú y los submenúes 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 LanguageFormatCommandobjetos y creará de forma recursiva nuevos menús y elementos de menú anexandolos a la parte inferior del menú existente (creado en el Generador de interfaces) 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 LanguageFormatCommand objeto que tenga una propiedad en blanco Title , esta rutina crea un elemento de menú Separador (una línea gris fina) entre las secciones de 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 LanguageFormatCommand objeto contiene objetos secundarios LanguageFormatCommand , se crea un submenú y AssembleMenu se llama al método de forma recursiva para compilar 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 LanguageFormatCommand objetos:

// 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 esa colección pasada a la AssembleMenu función (con el menú Formato establecido como base), se crearían los siguientes menús dinámicos y elementos de menú:

The new menu items in the running app

Quitar menús y elementos

Si necesita quitar cualquier elemento de menú o menú de la interfaz de usuario de la aplicación, puede usar el RemoveItemAt método de la NSMenu clase simplemente proporcionando el índice de base cero del elemento que se va a quitar.

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

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 de menú se crean en el Generador de interfaces 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 o hace clic con el botón secundario 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 queremos crear nuestros propios menús contextuales personalizados para un elemento de interfaz de usuario que hemos agregado a una ventana.

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

Setting the segue type in the Main dot storyboard file.

Hagamos lo siguiente:

  1. Arrastre una etiqueta desde el Inspector de biblioteca a la ventana Panel y establezca su texto en "Property":

    Editing the label's value

  2. A continuación, arrastre un menú desde el Inspector de biblioteca hasta el controlador de vista en la jerarquía de vistas y cambie el nombre de los tres elementos de menú predeterminados Documento, Texto y Fuente:

    The required menu items

  3. Ahora, arrastre el control desde la etiqueta de propiedad hasta el menú:

    Dragging to create a segue

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

    Setting the segue type by selecting menu from Outlets in the Label context menu.

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

    Setting the segue class

  6. Vuelva a Visual Studio para Mac para sincronizar y vuelva a Interface Builder.

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

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

    Configuring the action named propertyDocument.

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

    Repeating actions for the remaining menu items.

  10. Por último, cree una salida para la etiqueta de propiedad denominada propertyLabel:

    Configuring the outlet

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

Edite el archivo PanelViewController.cs y agregue el código siguiente:

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 propiedad en el panel, veremos nuestro menú contextual personalizado. Si seleccionamos y elemento en el menú, el valor de la etiqueta cambiará:

The contextual menu running

A continuación, echemos un vistazo a la creación de menús de 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 comentarios 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 reside en el lado derecho de la barra de menús de la aplicación y es la única barra de estado disponible actualmente en macOS.

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

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 proporciona acceso a la barra de estado de todo el sistema. var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable); crea un nuevo elemento de barra de estado. Desde allí 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:

The status bar menu running

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

Menús de acoplamiento personalizados

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

A custom dock menu

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 nuevo archivo, seleccione Definición de interfaz vacía de Xamarin.Mac>, use "DockMenu" para el nombre y haga clic en el botón Nuevo para crear el nuevo archivo DockMenu.xib:

    Adding an empty interface definition

  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

    Laying out the UI

  3. A continuación, vamos a conectar nuestros nuevos elementos de menú a nuestras acciones existentes que creamos para nuestro menú personalizado en la sección Agregar, Editar y eliminar menús anterior. Cambie al inspector de Conectar ion y seleccione el primer respondedor en la jerarquía de interfaz. Desplácese hacia abajo y busque la phraseAddress: acción. Arrastre una línea desde el círculo de esa acción al elemento de menú Dirección :

    Dragging a line to the Address menu item.

  4. Repita para todos los demás elementos de menú que los adjunte a sus acciones correspondientes:

    Repeating for other menu items attaching them to their corresponding actions.

  5. A continuación, seleccione la aplicación en la jerarquía de interfaz. En el inspector de Conectar ion, arrastre una línea desde el círculo de la dockMenu salida hasta el menú que acabamos de crear:

    Dragging the wire up the outlet

  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:

    Editing the Info.plist file

  8. Haga clic en la pestaña Origen de la parte inferior de la pantalla:

    Selecting the Source view

  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):

    Adding the DockMenu item

Ahora, si ejecutamos nuestra aplicación y hacemos clic con el botón derecho en su icono en Dock, se mostrarán los nuevos elementos de menú:

An example of the dock menu running

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 seleccionar cuando el usuario hace clic en él. Una lista desplegable es un tipo de botón emergente que normalmente se usa para seleccionar comandos específicos del 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. Edite el archivo Main.storyboard en Xcode y arrastre un botón emergente desde el Inspector de biblioteca a la ventana Panel que creamos en la sección Menús contextuales:

    Adding a popup button

  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

    Configuring the menu items

  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 Conectar ion y seleccione el primer respondedor en la jerarquía de interfaz. Desplácese hacia abajo y busque la phraseAddress: acción. Arrastre una línea desde el círculo de esa acción al elemento de menú Dirección :

    Dragging to wire up an action

  4. Repita para todos los demás elementos de menú que los adjunte a sus acciones correspondientes:

    All required actions

  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 en el elemento emergente, el texto de la vista de texto cambiará:

An example of the popup running

Puede crear y trabajar con listas desplegables de la misma manera que los botones emergentes. En lugar de adjuntar a la acción existente, podría 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. En primer lugar, examinamos la barra de menús de la aplicación y, a continuación, examinamos los menús de la barra de estado y los menús de acoplamiento personalizados. Por último, tratamos los menús emergentes y las listas desplegables.