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.
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:
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:
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:
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:
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:
:
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):
¿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:
Mientras que otros elementos de menú, como Cortar, Copiar y Pegar no lo son:
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:
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:
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:
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óncopy:
. - 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 cadaNSMenuItem
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ú:
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):
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:
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:
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:
Observe que el elemento de menú Abrir ahora está habilitado. Si lo seleccionamos, se mostrará el cuadro de diálogo abierto:
Si hacemos clic en el botón Abrir, se mostrará el mensaje de alerta:
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:
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:
Haga clic en el botón + situado en la parte inferior del Inspector de atributos para agregar una nueva acción personalizada:
Asígnele el mismo nombre que la acción personalizada que creó en el controlador de ventana:
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):
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:
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:
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:
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ú:
Arrastre los elementos del Menú principal desde el submenú en el que se encuentran actualmente:
El menú debería ser similar al siguiente:
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:
Volvamos al menú Formato y borremos el elemento de submenú Fuente. A continuación, seleccione el menú Formato y cámbiele el nombre "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ú:
Ahora vamos a hacer lo siguiente para crear nuestro menú:
Arrastre un elemento de Menú desde el Inspector de biblioteca a la barra de menús entre los menús Texto y Ventana:
Cambie el nombre del elemento "Frases":
A continuación, arrastre un Menú desde el Inspector de biblioteca:
Arrastre entonces Menú sobre el nuevo Elemento de menú que acabamos de crear y cámbiele el nombre a "Frases":
Ahora vamos a cambiar el nombre de los tres Elementos de menú de forma predeterminada "Dirección", "Fecha" y "Saludo":
Vamos a agregar un cuarto Elemento de menú arrastrando un Elemento de menú desde el Inspector de biblioteca y llamándolo "Firma":
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:
Hagamos lo siguiente:
Arrastre el control desde el elemento de menú Dirección al archivo AppDelegate.h.
Cambie el tipo de Conexión a Acción:
Escriba un Nombre de "phraseAddress" y presione el botón Conectar para crear la nueva acción:
Repita los pasos anteriores para los elementos de menú Fecha, Saludo y Firma:
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
:
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:
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ú:
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:
Hagamos lo siguiente:
Arrastre una Etiqueta del Inspector de bibliotecas a la ventana Panel y defina su texto como "Propiedad":
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:
Ahora arrastre desde la Etiqueta de propiedad al Menú:
En el cuadro de diálogo emergente, seleccione Menú:
Desde el Inspector de identidad, establezca la clase del controlador de vista a "PanelViewController":
Vuelva a Visual Studio para Mac para sincronizar y, a continuación, vuelva a Interface Builder.
Cambie al Editor de asistente y seleccione el archivo PanelViewController.h.
Cree una acción para el elemento de menú Documento llamada
propertyDocument
:Repita las acciones de creación para los elementos de menú restantes:
Por último, cree una salida para la Etiqueta de propiedad llamada
propertyLabel
: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á:
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:
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:
Vamos a crear un menú de acoplamiento personalizado para nuestra aplicación haciendo lo siguiente:
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:
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
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:Repita el proceso para todos los demás elementos del menú, asociándolos a sus acciones correspondientes:
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:Guarde los cambios y vuelva a Visual Studio para Mac para sincronizar con Xcode.
Haga doble clic en el archivo Info.plist para abrirlo para editarlo:
Haz clic en la pestaña Fuente, en la parte inferior de la pantalla:
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):
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ú:
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:
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:
Agregue un nuevo elemento de menú y establezca los títulos de los elementos del elemento emergente en: Dirección, Fecha, Saludo y Firma
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:Repita el proceso para todos los demás elementos del menú, asociándolos a sus acciones correspondientes:
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á:
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.