Cuadros de diálogo en Xamarin.Mac

Cuando se trabaja con C# y .NET en una aplicación de Xamarin.Mac, se tiene acceso a las mismas ventanas y paneles que un desarrollador que trabaje en Objective-C y Xcode. Dado que Xamarin.Mac se integra directamente con Xcode, puede usar Interface Builder de Xcode para crear y mantener las vistas de tabla (u opcionalmente crearlas directamente en código C#).

Aparece un cuadro de diálogo en respuesta a una acción del usuario y normalmente proporciona formas en que los usuarios pueden completar la acción. Un cuadro de diálogo requiere una respuesta del usuario antes de que se pueda cerrar.

Se pueden usar ventanas en un estado Modeless (por ejemplo, un editor de texto que puede tener varios documentos abiertos a la vez) o Modal (por ejemplo, un cuadro de diálogo Exportar que se debe descartar antes de que la aplicación pueda continuar).

An open dialog box

En este artículo, trataremos los aspectos básicos del trabajo con diálogos y ventanas modales en una aplicación Xamarin.Mac. Se recomienda encarecidamente primero revisar el artículo Hello, Mac; específicamente las secciones Introducción a Xcode e Interface Builder y Salidas y acciones, ya que se abordan conceptos clave y técnicas que usaremos en este artículo.

También puede echar un vistazo a la sección Exponer clases o métodos de C# a Objective-C del documento sobre el funcionamiento interno de Xamarin.Mac, ya que en ella se explican los atributos Register y Export que se usan para conectar las clases de C# a objetos Objective-C y elementos de la interfaz de usuario.

Introducción a los diálogos

Aparece un cuadro de diálogo en respuesta a una acción del usuario (como guardar un archivo) y proporciona una manera de que los usuarios completen esa acción. Un cuadro de diálogo requiere una respuesta del usuario antes de que se pueda cerrar.

Según Apple, hay tres maneras de presentar un cuadro de diálogo:

  • Modal de documento: un cuadro de diálogo modal de documento impide que el usuario haga nada más dentro de un documento determinado hasta que se descarte.
  • Modal de la aplicación: un cuadro de diálogo modal de la aplicación impide que el usuario interactúe con la aplicación hasta que se descarte.
  • Modeless A Modeless Dialog permite a los usuarios cambiar la configuración del cuadro de diálogo mientras interactúan con la ventana del documento.

Cualquier estándar NSWindow se puede usar como un cuadro de diálogo personalizado al mostrarlo de forma modal:

An example modal window

Hojas de diálogo modales de documento

Una Hoja es un cuadro de diálogo modal adjunto a una ventana de documento determinada, lo que impide que los usuarios interactúen con la ventana hasta que descarten el cuadro de diálogo. Una Hoja se adjunta a la ventana desde la que surge y solo se puede abrir una hoja para una ventana en cualquier momento.

An example modal sheet

Preferencias de Windows

Una ventana Preferencias es un cuadro de diálogo modeless que contiene la configuración de la aplicación que el usuario cambia con poca frecuencia. Las preferencias de Windows suelen incluir una barra de herramientas que permite al usuario cambiar entre diferentes grupos de configuración:

An example preference window

Abrir el cuadro de diálogo

El cuadro de diálogo Abrir proporciona a los usuarios una manera coherente de buscar y abrir un elemento en una aplicación:

A open dialog box

macOS proporciona cuadros de diálogo de instalación de impresión y página estándar que la aplicación puede mostrar para que los usuarios puedan tener una experiencia de impresión coherente en cada aplicación que usen.

El cuadro de diálogo imprimir se puede mostrar como un cuadro de diálogo flotante libre:

A print dialog box

O bien, se puede mostrar como una hoja:

A print sheet

El cuadro de diálogo Configurar página se puede mostrar como un cuadro de diálogo flotante libre:

A page setup dialog

O bien, se puede mostrar como una hoja:

A page setup sheet

Guardar cuadros de diálogo

El cuadro de diálogo Guardar proporciona a los usuarios una manera coherente de guardar un elemento en una aplicación. El cuadro de diálogo Guardar tiene dos estados: Mínimo (también conocido como Contraído):

A save dialog

Y el estado Expandido:

An expanded save dialog

El cuadro de diálogo Guardar Mínimo también se puede mostrar como una hoja:

A minimal save sheet

Como puede el cuadro de diálogo Guardar Expandido:

An expanded save sheet

Para más información, consulte la sección Diálogos de las Directrices de interfaz humana de OS X de Apple

Agregar una ventana modal a un proyecto

Aparte de la ventana principal del documento, es posible que una aplicación de Xamarin.Mac tenga que mostrar otros tipos de ventanas al usuario, como Preferencias o Paneles de inspectores.

Para agregar una nueva ventana, haga lo siguiente:

  1. En el Explorador de soluciones, abra el Main.storyboard archivo para editarlo en el Interface Builder de Xcode.

  2. Arrastre un nuevo controlador de vista a la Superficie de diseño:

    Selecting a View Controller from the Library

  3. En el Inspector de identidad, introduzca CustomDialogController para el Nombre de la clase:

    Setting the class name to CustomDialogController.

  4. Vuelva a Visual Studio para Mac, permita que se sincronice con Xcode y cree el CustomDialogController.h archivo.

  5. Vuelva a Xcode y diseñe la interfaz:

    Designing the UI in Xcode

  6. Cree un Segue modal desde la ventana principal de la aplicación al nuevo controlador de vistas arrastrando desde el elemento de la interfaz de usuario que abrirá el cuadro de diálogo en la ventana del diálogo. Asigne el identificadorModalSegue:

    A modal segue

  7. Conecte las Acciones y salidas:

    Configuring an Action

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

Edite el archivo CustomDialogController.cs para que quede de la siguiente manera:

using System;
using Foundation;
using AppKit;

namespace MacDialog
{
    public partial class CustomDialogController : NSViewController
    {
        #region Private Variables
        private string _dialogTitle = "Title";
        private string _dialogDescription = "Description";
        private NSViewController _presentor;
        #endregion

        #region Computed Properties
        public string DialogTitle {
            get { return _dialogTitle; }
            set { _dialogTitle = value; }
        }

        public string DialogDescription {
            get { return _dialogDescription; }
            set { _dialogDescription = value; }
        }

        public NSViewController Presentor {
            get { return _presentor; }
            set { _presentor = value; }
        }
        #endregion

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

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

            // Set initial title and description
            Title.StringValue = DialogTitle;
            Description.StringValue = DialogDescription;
        }
        #endregion

        #region Private Methods
        private void CloseDialog() {
            Presentor.DismissViewController (this);
        }
        #endregion

        #region Custom Actions
        partial void AcceptDialog (Foundation.NSObject sender) {
            RaiseDialogAccepted();
            CloseDialog();
        }

        partial void CancelDialog (Foundation.NSObject sender) {
            RaiseDialogCanceled();
            CloseDialog();
        }
        #endregion

        #region Events
        public EventHandler DialogAccepted;

        internal void RaiseDialogAccepted() {
            if (this.DialogAccepted != null)
                this.DialogAccepted (this, EventArgs.Empty);
        }

        public EventHandler DialogCanceled;

        internal void RaiseDialogCanceled() {
            if (this.DialogCanceled != null)
                this.DialogCanceled (this, EventArgs.Empty);
        }
        #endregion
    }
}

Este código expone algunas propiedades para establecer el título y la descripción del cuadro de diálogo y algunos eventos para reaccionar al diálogo que se va a cancelar o aceptar.

Para ello, edite el archivo ViewController.cs, invalide el método PrepareForSegue y haga que tenga un aspecto similar al siguiente:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on the segue name
    switch (segue.Identifier) {
    case "ModalSegue":
        var dialog = segue.DestinationController as CustomDialogController;
        dialog.DialogTitle = "MacDialog";
        dialog.DialogDescription = "This is a sample dialog.";
        dialog.DialogAccepted += (s, e) => {
            Console.WriteLine ("Dialog accepted");
            DismissViewController (dialog);
        };
        dialog.Presentor = this;
        break;
    }
}

Este código inicializa el segue que definimos en el Interface Builder de Xcode en nuestro cuadro de diálogo y configura el título y la descripción. También controla la elección que realiza el usuario en el cuadro de diálogo.

Podemos ejecutar nuestra aplicación y mostrar el cuadro de diálogo personalizado:

An example dialog

Para obtener más información sobre el uso de ventanas en una aplicación de Xamarin.Mac, consulte nuestra documentación Trabajar con Windows.

Crear una hoja personalizada

Una Hoja es un cuadro de diálogo modal adjunto a una ventana de documento determinada, lo que impide que los usuarios interactúen con la ventana hasta que descarten el cuadro de diálogo. Una Hoja se adjunta a la ventana desde la que surge y solo se puede abrir una hoja para una ventana en cualquier momento.

Para crear una hoja personalizada en Xamarin.Mac, hagamos lo siguiente:

  1. En el Explorador de soluciones, abra el Main.storyboard archivo para editarlo en el Interface Builder de Xcode.

  2. Arrastre un nuevo controlador de vista a la Superficie de diseño:

    Selecting a View Controller from the Library

  3. Diseñe la interfaz de usuario:

    The UI design

  4. Cree un segue de hoja desde la ventana principal al nuevo controlador de vistas:

    Selecting the Sheet segue type

  5. En el Inspector de identidad, asigne un nombre a la clase SheetViewControllerdel controlador de vista:

    Setting the class name to SheetViewController.

  6. Defina las salidas y accionesnecesarias:

    Defining the required Outlets and Actions

  7. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizar.

A continuación, edite el archivo SheetViewController.cs y haga que tenga un aspecto similar al siguiente:

using System;
using Foundation;
using AppKit;

namespace MacDialog
{
    public partial class SheetViewController : NSViewController
    {
        #region Private Variables
        private string _userName = "";
        private string _password = "";
        private NSViewController _presentor;
        #endregion

        #region Computed Properties
        public string UserName {
            get { return _userName; }
            set { _userName = value; }
        }

        public string Password {
            get { return _password;}
            set { _password = value;}
        }

        public NSViewController Presentor {
            get { return _presentor; }
            set { _presentor = value; }
        }
        #endregion

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

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

            // Set initial values
            NameField.StringValue = UserName;
            PasswordField.StringValue = Password;

            // Wireup events
            NameField.Changed += (sender, e) => {
                UserName = NameField.StringValue;
            };
            PasswordField.Changed += (sender, e) => {
                Password = PasswordField.StringValue;
            };
        }
        #endregion

        #region Private Methods
        private void CloseSheet() {
            Presentor.DismissViewController (this);
        }
        #endregion

        #region Custom Actions
        partial void AcceptSheet (Foundation.NSObject sender) {
            RaiseSheetAccepted();
            CloseSheet();
        }

        partial void CancelSheet (Foundation.NSObject sender) {
            RaiseSheetCanceled();
            CloseSheet();
        }
        #endregion

        #region Events
        public EventHandler SheetAccepted;

        internal void RaiseSheetAccepted() {
            if (this.SheetAccepted != null)
                this.SheetAccepted (this, EventArgs.Empty);
        }

        public EventHandler SheetCanceled;

        internal void RaiseSheetCanceled() {
            if (this.SheetCanceled != null)
                this.SheetCanceled (this, EventArgs.Empty);
        }
        #endregion
    }
}

A continuación, edite el archivo ViewController.cs, edite el método PrepareForSegue y haga que tenga el siguiente aspecto:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on the segue name
    switch (segue.Identifier) {
    case "ModalSegue":
        var dialog = segue.DestinationController as CustomDialogController;
        dialog.DialogTitle = "MacDialog";
        dialog.DialogDescription = "This is a sample dialog.";
        dialog.DialogAccepted += (s, e) => {
            Console.WriteLine ("Dialog accepted");
            DismissViewController (dialog);
        };
        dialog.Presentor = this;
        break;
    case "SheetSegue":
        var sheet = segue.DestinationController as SheetViewController;
        sheet.SheetAccepted += (s, e) => {
            Console.WriteLine ("User Name: {0} Password: {1}", sheet.UserName, sheet.Password);
        };
        sheet.Presentor = this;
        break;
    }
}

Si ejecutamos la aplicación y abremos la hoja, se adjuntará a la ventana:

An example sheet

Crear un cuadro de diálogo de preferencias

Antes de diseñar la vista de preferencias en el Interface Builder, tendremos que agregar un tipo de segue personalizado para controlar la conmutación de las preferencias. Agregue una nueva clase al proyecto y nómbrelo ReplaceViewSeque. Edite la clase y haga que tenga el siguiente aspecto:

using System;
using AppKit;
using Foundation;

namespace MacWindows
{
    [Register("ReplaceViewSeque")]
    public class ReplaceViewSeque : NSStoryboardSegue
    {
        #region Constructors
        public ReplaceViewSeque() {

        }

        public ReplaceViewSeque (string identifier, NSObject sourceController, NSObject destinationController) : base(identifier,sourceController,destinationController) {

        }

        public ReplaceViewSeque (IntPtr handle) : base(handle) {
        }

        public ReplaceViewSeque (NSObjectFlag x) : base(x) {
        }
        #endregion

        #region Override Methods
        public override void Perform ()
        {
            // Cast the source and destination controllers
            var source = SourceController as NSViewController;
            var destination = DestinationController as NSViewController;

            // Is there a source?
            if (source == null) {
                // No, get the current key window
                var window = NSApplication.SharedApplication.KeyWindow;

                // Swap the controllers
                window.ContentViewController = destination;

                // Release memory
                window.ContentViewController?.RemoveFromParentViewController ();
            } else {
                // Swap the controllers
                source.View.Window.ContentViewController = destination;

                // Release memory
                source.RemoveFromParentViewController ();
            }
        
        }
        #endregion

    }

}

Con el segue personalizado creado, podemos agregar una nueva ventana en el Interface Builder de Xcode para controlar nuestras preferencias.

Para agregar una nueva ventana, haga lo siguiente:

  1. En el Explorador de soluciones, abra el Main.storyboard archivo para editarlo en el Interface Builder de Xcode.

  2. Arrastre un nuevo controlador de ventana a la Superficie de diseño:

    Select a Window Controller from the Library

  3. Organice la ventana cerca del diseñador de la barra de menús:

    Adding the new Window

  4. Cree copias del controlador de vistas adjunto, ya que habrá pestañas en la vista de preferencias:

    Adding the required View Controllers

  5. Arrastre un nuevo controlador de barra de herramientas desde la biblioteca:

    Select a Toolbar Controller from the Library

  6. Y colóquelo en la ventana de la Superficie de diseño:

    Adding a new Toolbar Controller

  7. Diseñe el diseño de la barra de herramientas:

    Layout the toolbar

  8. Control-Haga clic y arrastre desde cada botón de barra de herramientas a las vistas que creó anteriormente. Seleccione un tipo de segue personalizado:

    Setting a Custom segue type.

  9. Seleccione el nuevo Segue y establezca la clase en ReplaceViewSegue:

    Setting the segue class

  10. En el Diseñador de la barra de menús de la Superficie de diseño, en el menú Aplicación, seleccione Preferencias..., haga clic en el control y arrástrelo hasta la ventana Preferencias para crear un elemento Mostrar segue:

    Setting the segue type by dragging Preferences to the Preferences Window.

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

Si ejecutamos el código y selecciona lasPreferencias... en el Menú aplicación, se mostrará la ventana:

An example preferences window displaying the word Profile.

Para obtener más información sobre cómo trabajar con Windows y Barras de herramientas, consulte nuestra documentación sobre Windows y Barras de herramientas.

Guardar y cargar preferencias

En una aplicación macOS típica, cuando el usuario realiza cambios en cualquiera de las preferencias de usuario de la aplicación, esos cambios se guardan automáticamente. La manera más fácil de controlar esto en una aplicación de Xamarin.Mac es crear una sola clase para administrar todas las preferencias del usuario y compartirla en todo el sistema.

En primer lugar, agregue una nueva clase AppPreferences al proyecto y herede de NSObject. Las preferencias se diseñarán para usar el enlace de datos y la codificación clave-valor, lo que hará que el proceso de creación y mantenimiento de las preferencias sea mucho más sencillo. Dado que las Preferencias constarán de una pequeña cantidad de tipos de datos simples, use el integrado para NSUserDefaults almacenar y recuperar valores.

Edite el archivo AppPreferences.cs para que quede de la siguiente manera:

using System;
using Foundation;
using AppKit;

namespace SourceWriter
{
    [Register("AppPreferences")]
    public class AppPreferences : NSObject
    {
        #region Computed Properties
        [Export("DefaultLanguage")]
        public int DefaultLanguage {
            get { 
                var value = LoadInt ("DefaultLanguage", 0);
                return value; 
            }
            set {
                WillChangeValue ("DefaultLanguage");
                SaveInt ("DefaultLanguage", value, true);
                DidChangeValue ("DefaultLanguage");
            }
        }

        [Export("SmartLinks")]
        public bool SmartLinks {
            get { return LoadBool ("SmartLinks", true); }
            set {
                WillChangeValue ("SmartLinks");
                SaveBool ("SmartLinks", value, true);
                DidChangeValue ("SmartLinks");
            }
        }

        // Define any other required user preferences in the same fashion
        ...

        [Export("EditorBackgroundColor")]
        public NSColor EditorBackgroundColor {
            get { return LoadColor("EditorBackgroundColor", NSColor.White); }
            set {
                WillChangeValue ("EditorBackgroundColor");
                SaveColor ("EditorBackgroundColor", value, true);
                DidChangeValue ("EditorBackgroundColor");
            }
        }
        #endregion

        #region Constructors
        public AppPreferences ()
        {
        }
        #endregion

        #region Public Methods
        public int LoadInt(string key, int defaultValue) {
            // Attempt to read int
            var number = NSUserDefaults.StandardUserDefaults.IntForKey(key);

            // Take action based on value
            if (number == null) {
                return defaultValue;
            } else {
                return (int)number;
            }
        }
            
        public void SaveInt(string key, int value, bool sync) {
            NSUserDefaults.StandardUserDefaults.SetInt(value, key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }

        public bool LoadBool(string key, bool defaultValue) {
            // Attempt to read int
            var value = NSUserDefaults.StandardUserDefaults.BoolForKey(key);

            // Take action based on value
            if (value == null) {
                return defaultValue;
            } else {
                return value;
            }
        }

        public void SaveBool(string key, bool value, bool sync) {
            NSUserDefaults.StandardUserDefaults.SetBool(value, key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }

        public string NSColorToHexString(NSColor color, bool withAlpha) {
            //Break color into pieces
            nfloat red=0, green=0, blue=0, alpha=0;
            color.GetRgba (out red, out green, out blue, out alpha);

            // Adjust to byte
            alpha *= 255;
            red *= 255;
            green *= 255;
            blue *= 255;

            //With the alpha value?
            if (withAlpha) {
                return String.Format ("#{0:X2}{1:X2}{2:X2}{3:X2}", (int)alpha, (int)red, (int)green, (int)blue);
            } else {
                return String.Format ("#{0:X2}{1:X2}{2:X2}", (int)red, (int)green, (int)blue);
            }
        }

        public NSColor NSColorFromHexString (string hexValue)
        {
            var colorString = hexValue.Replace ("#", "");
            float red, green, blue, alpha;

            // Convert color based on length
            switch (colorString.Length) {
            case 3 : // #RGB
                red = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(0, 1)), 16) / 255f;
                green = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(1, 1)), 16) / 255f;
                blue = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(2, 1)), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, 1.0f);
            case 6 : // #RRGGBB
                red = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
                green = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
                blue = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, 1.0f);
            case 8 : // #AARRGGBB
                alpha = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
                red = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
                green = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
                blue = Convert.ToInt32(colorString.Substring(6, 2), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, alpha);
            default :
                throw new ArgumentOutOfRangeException(string.Format("Invalid color value '{0}'. It should be a hex value of the form #RBG, #RRGGBB or #AARRGGBB", hexValue));
            }
        }

        public NSColor LoadColor(string key, NSColor defaultValue) {

            // Attempt to read color
            var hex = NSUserDefaults.StandardUserDefaults.StringForKey(key);

            // Take action based on value
            if (hex == null) {
                return defaultValue;
            } else {
                return NSColorFromHexString (hex);
            }
        }

        public void SaveColor(string key, NSColor color, bool sync) {
            // Save to default
            NSUserDefaults.StandardUserDefaults.SetString(NSColorToHexString(color,true), key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }
        #endregion
    }
}

Esta clase contiene algunas rutinas auxiliares como SaveInt, LoadInt, SaveColor, LoadColoretc. para facilitar el trabajo NSUserDefaults. Además, dado que NSUserDefaults no tiene una manera integrada de controlar NSColors, los NSColorToHexString métodos y NSColorFromHexString se usan para convertir colores en cadenas hexadecimales basadas en web (#RRGGBBAA donde AA es la transparencia alfa) que se pueden almacenar y recuperar fácilmente.

En el archivo AppDelegate.cs, cree una instancia del objeto AppPreferences que se usará en toda la aplicación:

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

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

        public AppPreferences Preferences { get; set; } = new AppPreferences();
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion
        
        ...

Preferencias de cableado a vistas de preferencias

A continuación, conecte la clase Preference a los elementos de la interfaz de usuario en la ventana de preferencias y las vistas creadas anteriormente. En el Interface Builder, seleccione un controlador de vista de preferencias y cambie al Inspector de identidad, cree una clase personalizada para el controlador:

The Identity Inspector

Vuelva a Visual Studio para Mac para sincronizar los cambios y abra la clase recién creada para su edición. Haga que la clase se parezca a lo siguiente:

using System;
using Foundation;
using AppKit;

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

        #region Computed Properties
        [Export("Preferences")]
        public AppPreferences Preferences {
            get { return App.Preferences; }
        }
        #endregion

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

Tenga en cuenta que esta clase ha hecho dos cosas aquí: En primer lugar, hay una propiedad auxiliar App para facilitar el acceso a AppDelegate. En segundo lugar, la Preferences propiedad expone la clase AppPreferences global para el enlace de datos con los controles de interfaz de usuario colocados en esta vista.

A continuación, haga doble clic en el archivo Storyboard para volver a abrirlo en el Interface Builder (y vea los cambios realizados anteriormente). Arrastre los controles de interfaz de usuario necesarios para crear la interfaz de preferencias en la vista. Para cada control, cambie al Inspector de enlace y enlace a las propiedades individuales de la clase AppPreference:

The Binding Inspector

Repita los pasos anteriores para todos los paneles (Controladores de vista) y Propiedades de preferencia necesarios.

Aplicación de cambios de preferencias a todas las ventanas abiertas

Como se indicó anteriormente, en una aplicación macOS típica, cuando el usuario realiza cambios en cualquiera de las preferencias de usuario de la aplicación, esos cambios se guardan automáticamente y se aplican a cualquier ventana que el usuario pueda tener abierta en la aplicación.

La planeación y el diseño cuidadosos de las preferencias y ventanas de la aplicación permitirán que este proceso se produzca sin problemas y transparentemente al usuario final, con una cantidad mínima de trabajo de codificación.

Para cualquier ventana que consuma preferencias de aplicación, agregue la siguiente propiedad auxiliar a su controlador de vista de contenido para facilitar el acceso a AppDelegate:

#region Application Access
public static AppDelegate App {
    get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
}
#endregion

A continuación, agregue una clase para configurar el contenido o el comportamiento en función de las preferencias del usuario:

public void ConfigureEditor() {

    // General Preferences
    TextEditor.AutomaticLinkDetectionEnabled = App.Preferences.SmartLinks;
    TextEditor.AutomaticQuoteSubstitutionEnabled = App.Preferences.SmartQuotes;
    ...

}

Debe llamar al método de configuración cuando se abra la ventana por primera vez para asegurarse de que se ajusta a las preferencias del usuario:

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

    // Configure editor from user preferences
    ConfigureEditor ();
    ...
}

A continuación, edite el AppDelegate.cs archivo y agregue el método siguiente para aplicar los cambios de preferencia a todas las ventanas abiertas:

public void UpdateWindowPreferences() {

    // Process all open windows
    for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
        var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
        if (content != null ) {
            // Reformat all text
            content.ConfigureEditor ();
        }
    }

}

A continuación, agregue una nueva clase PreferenceWindowDelegate al proyecto y haga que tenga un aspecto similar al siguiente:

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

namespace SourceWriter
{
    public class PreferenceWindowDelegate : NSWindowDelegate
    {
        #region Application Access
        public static AppDelegate App {
            get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
        }
        #endregion

        #region Computed Properties
        public NSWindow Window { get; set;}
        #endregion

        #region constructors
        public PreferenceWindowDelegate (NSWindow window)
        {
            // Initialize
            this.Window = window;

        }
        #endregion

        #region Override Methods
        public override bool WindowShouldClose (Foundation.NSObject sender)
        {
            
            // Apply any changes to open windows
            App.UpdateWindowPreferences();

            return true;
        }
        #endregion
    }
}

Esto hará que los cambios de preferencia se envíen a todas las ventanas abiertas cuando se cierre la ventana de preferencias.

Por último, edite el controlador de ventana de preferencias y agregue el delegado creado anteriormente:

using System;
using Foundation;
using AppKit;

namespace SourceWriter
{
    public partial class PreferenceWindowController : NSWindowController
    {
        #region Constructors
        public PreferenceWindowController (IntPtr handle) : base (handle)
        {
        }
        #endregion

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

            // Initialize
            Window.Delegate = new PreferenceWindowDelegate(Window);
            Toolbar.SelectedItemIdentifier = "General";
        }
        #endregion
    }
}

Con todos estos cambios implementados, si el usuario edita las preferencias de la aplicación y cierra la ventana preferencias, los cambios se aplicarán a todas las ventanas abiertas:

An example Preferences Window, displayed with several other open windows.

Cuadro de diálogo Abrir

El cuadro de diálogo Abrir proporciona a los usuarios una manera coherente de buscar y abrir un elemento en una aplicación. Para mostrar un cuadro de diálogo abrir en una aplicación de Xamarin.Mac, use el código siguiente:

var dlg = NSOpenPanel.OpenPanel;
dlg.CanChooseFiles = true;
dlg.CanChooseDirectories = false;
dlg.AllowedFileTypes = new string[] { "txt", "html", "md", "css" };

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

    if (url != null) {
        var path = url.Path;

        // Create a new window to hold the text
        var newWindowController = new MainWindowController ();
        newWindowController.Window.MakeKeyAndOrderFront (this);

        // Load the text into the window
        var window = newWindowController.Window as MainWindow;
        window.Text = File.ReadAllText(path);
        window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
        window.RepresentedUrl = url;

    }
}

En el código anterior, estamos abriendo una nueva ventana de documento para mostrar el contenido del archivo. Deberá reemplazar este código por la funcionalidad que requiere la aplicación.

Las siguientes propiedades están disponibles al trabajar con NSOpenPanel:

  • CanChooseFiles : si true el usuario puede seleccionar archivos.
  • CanChooseDirectories : si true el usuario puede seleccionar directorios.
  • AllowsMultipleSelection : si true el usuario puede seleccionar más de un archivo a la vez.
  • ResolveAliases : si true selecciona y alias, lo resuelve en la ruta de acceso del archivo original.
  • AllowedFileTypes : es una matriz de cadenas de tipos de archivo que el usuario puede seleccionar como una extensión o UTI. El valor predeterminado es null, que permite abrir cualquier archivo.

El RunModal () método muestra el cuadro de diálogo Abrir y permite al usuario seleccionar archivos o directorios (según lo especificado por las propiedades) y devuelve 1 si el usuario hace clic en el botón Abrir.

El cuadro de diálogo Abrir devuelve los archivos o directorios seleccionados del usuario como una matriz de direcciones URL en la URL propiedad.

Si ejecutamos el programa y seleccionamos el elemento Abrir... en el menú Archivo, se muestra lo siguiente:

An open dialog box

Cuadros de diálogo imprimir y configurar páginas

macOS proporciona cuadros de diálogo de instalación de impresión y página estándar que la aplicación puede mostrar para que los usuarios puedan tener una experiencia de impresión coherente en cada aplicación que usen.

El código siguiente mostrará el cuadro de diálogo de impresión estándar:

public bool ShowPrintAsSheet { get; set;} = true;
...

[Export ("showPrinter:")]
void ShowDocument (NSObject sender) {
    var dlg = new NSPrintPanel();

    // Display the print dialog as dialog box
    if (ShowPrintAsSheet) {
        dlg.BeginSheet(new NSPrintInfo(),this,this,null,new IntPtr());
    } else {
        if (dlg.RunModalWithPrintInfo(new NSPrintInfo()) == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to print the document here...",
                MessageText = "Print Document",
            };
            alert.RunModal ();
        }
    }
}

Si establecemos la ShowPrintAsSheet propiedad en false, ejecute la aplicación y muestre el cuadro de diálogo de impresión, se mostrará lo siguiente:

A print dialog box

Si establece la ShowPrintAsSheet propiedad en true, ejecute la aplicación y muestre el cuadro de diálogo de impresión, se mostrará lo siguiente:

A print sheet

El código siguiente mostrará el cuadro de diálogo Diseño de página:

[Export ("showLayout:")]
void ShowLayout (NSObject sender) {
    var dlg = new NSPageLayout();

    // Display the print dialog as dialog box
    if (ShowPrintAsSheet) {
        dlg.BeginSheet (new NSPrintInfo (), this);
    } else {
        if (dlg.RunModal () == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to print the document here...",
                MessageText = "Print Document",
            };
            alert.RunModal ();
        }
    }
}

Si establecemos la ShowPrintAsSheet propiedad en false, ejecute la aplicación y muestre el cuadro de diálogo diseño de impresión, se mostrará lo siguiente:

A page setup dialog

Si establece la ShowPrintAsSheet propiedad en true, ejecute la aplicación y muestre el cuadro de diálogo diseño de impresión, se mostrará lo siguiente:

A page setup sheet

Para obtener más información sobre cómo trabajar con los cuadros de diálogo imprimir y configurar páginas, consulte la documentación de NSPrintPanel y NSPageLayout de Apple.

Cuadro de diálogo Guardar

El cuadro de diálogo Guardar proporciona a los usuarios una manera coherente de guardar un elemento en una aplicación.

El código siguiente mostrará el cuadro de diálogo Guardar estándar:

public bool ShowSaveAsSheet { get; set;} = true;
...

[Export("saveDocumentAs:")]
void ShowSaveAs (NSObject sender)
{
    var dlg = new NSSavePanel ();
    dlg.Title = "Save Text File";
    dlg.AllowedFileTypes = new string[] { "txt", "html", "md", "css" };

    if (ShowSaveAsSheet) {
        dlg.BeginSheet(mainWindowController.Window,(result) => {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to save the document here...",
                MessageText = "Save Document",
            };
            alert.RunModal ();
        });
    } else {
        if (dlg.RunModal () == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to save the document here...",
                MessageText = "Save Document",
            };
            alert.RunModal ();
        }
    }

}

La AllowedFileTypes propiedad es una matriz de cadenas de tipos de archivo que el usuario puede seleccionar para guardar el archivo como. El tipo de archivo se puede especificar como una extensión o UTI. El valor predeterminado es null, que permite usar cualquier tipo de archivo.

Si establecemos la ShowSaveAsSheet propiedad en false, ejecute la aplicación y seleccione Guardar como... en el menú Archivo, se mostrará lo siguiente:

A save dialog box

El usuario puede expandir el cuadro de diálogo:

An expanded save dialog box

Si establecemos la ShowSaveAsSheet propiedad en true, ejecute la aplicación y seleccione Guardar como... en el menú Archivo, se mostrará lo siguiente:

A save sheet

El usuario puede expandir el cuadro de diálogo:

An expanded save sheet

Para obtener más información sobre cómo trabajar con el cuadro de diálogo Guardar, consulte la documentación de NSSavePanelde Apple.

Resumen

En este artículo se ha tomado un vistazo detallado al trabajo con Ventanas modales, Hojas y cuadros de diálogo del sistema estándar en una aplicación de Xamarin.Mac. Hemos visto los diferentes tipos y usos de Ventanas modales, hojas y diálogos, cómo crear y mantener ventanas modales y hojas en el Interface Builder de Xcode y cómo trabajar con ventanas modales, hojas y diálogos en código de C#.