Compartir a través de


Copiar y pegar en Xamarin.Mac

En este artículo, se explica cómo trabajar con el área de montaje para proporcionar la opción de copiar y pegar en una aplicación de Xamarin.Mac. Muestra cómo trabajar con tipos de datos estándar que se pueden compartir entre varias aplicaciones y cómo admitir datos personalizados dentro de una aplicación determinada.

Información general

Al trabajar con C# y .NET en una aplicación de Xamarin.Mac, tiene acceso al mismo panel de pegado (copiar y pegar) que tiene un desarrollador que trabaja en Objective-C.

En este artículo se tratarán las dos formas principales de usar el área de montaje en una aplicación de Xamarin.Mac:

  1. Tipos de datos estándar: dado que las operaciones de pegado se realizan normalmente entre dos aplicaciones no relacionadas, ninguna aplicación conoce los tipos de datos que admite el otro. Para maximizar el potencial de uso compartido, el área de montaje puede contener varias representaciones de un elemento determinado (mediante un conjunto estándar de tipos de datos comunes), lo que permite que la aplicación de consumo elija la versión más adecuada para sus necesidades.
  2. Datos personalizados: para admitir la copia y el pegado de datos complejos dentro de Xamarin.Mac, puede definir un tipo de datos personalizado que controlará el panel de pegado. Por ejemplo, una aplicación de dibujo vectorial que permite al usuario copiar y pegar formas complejas compuestas de varios tipos de datos y puntos.

Ejemplo de la aplicación en ejecución

En este artículo, trataremos los conceptos básicos de trabajar con el panel de pegado en una aplicación de Xamarin.Mac para admitir operaciones de copia y pegado. 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.

Introducción del área de montaje

El área de montaje presenta un mecanismo estandarizado para intercambiar datos dentro de una aplicación determinada o entre aplicaciones. El uso típico de un área de montaje en una aplicación de Xamarin.Mac es controlar las operaciones de copia y pegado, pero también se admiten una serie de otras operaciones (como Arrastrar & anular y servicio de aplicación).

Para sacarle el suelo rápidamente, vamos a empezar con una introducción sencilla y práctica al uso de tablas de pegado en una aplicación de Xamarin.Mac. Más adelante, proporcionaremos una explicación detallada de cómo funciona el área de montaje y los métodos usados.

En este ejemplo, vamos a crear una aplicación sencilla basada en documentos que administra una ventana que contiene una vista de imagen. El usuario podrá copiar y pegar imágenes entre documentos de la aplicación y hacia o desde otras aplicaciones o desde varias ventanas dentro de la misma aplicación.

Creación del proyecto de Xamarin

En primer lugar, vamos a crear una nueva aplicación de Xamarin.Mac basada en documentos para la que agregaremos soporte con copiar y pegar.

Haga lo siguiente:

  1. Inicie Visual Studio para Mac y haga clic en el vínculo Nuevo proyecto....

  2. Seleccione Mac>App>Cocoa Appy haga clic en el botón Siguiente :

    Creación de un nuevo proyecto de aplicación Cocoa

  3. Escriba MacCopyPaste para el nombre del proyecto de y mantenga todo lo demás como predeterminado. Haga clic en Siguiente:

    Establecimiento del nombre del proyecto

  4. Haga clic en el botón Crear :

    Confirmación de la nueva configuración del proyecto

Agregar un NSDocument

A continuación, agregaremos una clase de NSDocument personalizada que actuará como almacenamiento en segundo plano para la interfaz de usuario de la aplicación. Contendrá una sola vista de imagen y sabrá cómo copiar una imagen de la vista en el panel de pegado predeterminado y cómo tomar una imagen del panel de pegado predeterminado y mostrarla en la vista de imagen.

Haga clic con el botón derecho en el proyecto de Xamarin.Mac en el Panel de solución y seleccione Agregar>Nuevo archivo..:

Adición de un NSDocument al proyecto

Escriba ImageDocument para el Nombre y haga clic en el botón Nuevo. Edite la clase ImageDocument.cs y haga que tenga un aspecto similar al siguiente:

using System;
using AppKit;
using Foundation;
using ObjCRuntime;

namespace MacCopyPaste
{
    [Register("ImageDocument")]
    public class ImageDocument : NSDocument
    {
        #region Computed Properties
        public NSImageView ImageView {get; set;}

        public ImageInfo Info { get; set; } = new ImageInfo();

        public bool ImageAvailableOnPasteboard {
            get {
                // Initialize the pasteboard
                NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
                Class [] classArray  = { new Class ("NSImage") };

                // Check to see if an image is on the pasteboard
                return pasteboard.CanReadObjectForClasses (classArray, null);
            }
        }
        #endregion

        #region Constructor
        public ImageDocument ()
        {
        }
        #endregion

        #region Public Methods
        [Export("CopyImage:")]
        public void CopyImage(NSObject sender) {

            // Grab the current image
            var image = ImageView.Image;

            // Anything to process?
            if (image != null) {
                // Get the standard pasteboard
                var pasteboard = NSPasteboard.GeneralPasteboard;

                // Empty the current contents
                pasteboard.ClearContents();

                // Add the current image to the pasteboard
                pasteboard.WriteObjects (new NSImage[] {image});

                // Save the custom data class to the pastebaord
                pasteboard.WriteObjects (new ImageInfo[] { Info });

                // Using a Pasteboard Item
                NSPasteboardItem item = new NSPasteboardItem();
                string[] writableTypes = {"public.text"};

                // Add a data provier to the item
                ImageInfoDataProvider dataProvider = new ImageInfoDataProvider (Info.Name, Info.ImageType);
                var ok = item.SetDataProviderForTypes (dataProvider, writableTypes);

                // Save to pasteboard
                if (ok) {
                    pasteboard.WriteObjects (new NSPasteboardItem[] { item });
                }
            }

        }

        [Export("PasteImage:")]
        public void PasteImage(NSObject sender) {

            // Initialize the pasteboard
            NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
            Class [] classArray  = { new Class ("NSImage") };

            bool ok = pasteboard.CanReadObjectForClasses (classArray, null);
            if (ok) {
                // Read the image off of the pasteboard
                NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray, null);
                NSImage image = (NSImage)objectsToPaste[0];

                // Display the new image
                ImageView.Image = image;
            }

            Class [] classArray2 = { new Class ("ImageInfo") };
            ok = pasteboard.CanReadObjectForClasses (classArray2, null);
            if (ok) {
                // Read the image off of the pasteboard
                NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray2, null);
                ImageInfo info = (ImageInfo)objectsToPaste[0];

            }

        }
        #endregion
    }
}

Echemos un vistazo a algunos de los códigos en detalle a continuación.

El código siguiente proporciona una propiedad para probar la existencia de datos de imagen en el panel de pegado predeterminado, si hay disponible una imagen, se devuelve true de lo contrario false:

public bool ImageAvailableOnPasteboard {
    get {
        // Initialize the pasteboard
        NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
        Class [] classArray  = { new Class ("NSImage") };

        // Check to see if an image is on the pasteboard
        return pasteboard.CanReadObjectForClasses (classArray, null);
    }
}

El código siguiente copia una imagen de la vista de imagen adjunta en el panel de pegado predeterminado:

[Export("CopyImage:")]
public void CopyImage(NSObject sender) {

    // Grab the current image
    var image = ImageView.Image;

    // Anything to process?
    if (image != null) {
        // Get the standard pasteboard
        var pasteboard = NSPasteboard.GeneralPasteboard;

        // Empty the current contents
        pasteboard.ClearContents();

        // Add the current image to the pasteboard
        pasteboard.WriteObjects (new NSImage[] {image});

        // Save the custom data class to the pastebaord
        pasteboard.WriteObjects (new ImageInfo[] { Info });

        // Using a Pasteboard Item
        NSPasteboardItem item = new NSPasteboardItem();
        string[] writableTypes = {"public.text"};

        // Add a data provider to the item
        ImageInfoDataProvider dataProvider = new ImageInfoDataProvider (Info.Name, Info.ImageType);
        var ok = item.SetDataProviderForTypes (dataProvider, writableTypes);

        // Save to pasteboard
        if (ok) {
            pasteboard.WriteObjects (new NSPasteboardItem[] { item });
        }
    }

}

Y el código siguiente pega una imagen del área de montaje predeterminado y la muestra en la vista de imagen adjunta (si el panel de pegado contiene una imagen válida):

[Export("PasteImage:")]
public void PasteImage(NSObject sender) {

    // Initialize the pasteboard
    NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
    Class [] classArray  = { new Class ("NSImage") };

    bool ok = pasteboard.CanReadObjectForClasses (classArray, null);
    if (ok) {
        // Read the image off of the pasteboard
        NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray, null);
        NSImage image = (NSImage)objectsToPaste[0];

        // Display the new image
        ImageView.Image = image;
    }

    Class [] classArray2 = { new Class ("ImageInfo") };
    ok = pasteboard.CanReadObjectForClasses (classArray2, null);
    if (ok) {
        // Read the image off of the pasteboard
        NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray2, null);
        ImageInfo info = (ImageInfo)objectsToPaste[0]
    }
}

Con este documento en su lugar, crearemos la interfaz de usuario para la aplicación Xamarin.Mac.

Creación de la interfaz de usuario

Haga doble clic en el archivo Main.storyboard para abrirlo en Xcode. A continuación, agregue una barra de herramientas y una imagen bien y configúrelas de la siguiente manera:

Edición de la barra de herramientas

Agregue una copia y pegue elemento de la barra de herramientas de imagen al lado izquierdo de la barra de herramientas. Los usaremos como accesos directos para copiar y pegar en el menú Editar. A continuación, agregue cuatro elementos de la barra de herramientas de imágenes al lado derecho de la barra de herramientas. Los usaremos para rellenar la imagen correctamente con algunas imágenes predeterminadas.

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

A continuación, vamos a exponer las siguientes salidas y acciones para los elementos de la barra de herramientas y el pozo de imagen:

Creación de salidas y acciones

Para obtener más información sobre cómo trabajar con salidas y acciones, consulte la sección Salidas y acciones de nuestra documentación de Hello, Mac.

Habilitación de la interfaz de usuario

Con nuestra interfaz de usuario creada en Xcode y nuestro elemento de interfaz de usuario expuesto a través de salidas y acciones, es necesario agregar el código para habilitar la interfaz de usuario. Haga doble clic en el archivo ImageWindow.cs del Panel de solución y fíjelo como el siguiente:

using System;
using Foundation;
using AppKit;

namespace MacCopyPaste
{
    public partial class ImageWindow : NSWindow
    {
        #region Private Variables
        ImageDocument document;
        #endregion

        #region Computed Properties
        [Export ("Document")]
        public ImageDocument Document {
            get {
                return document;
            }
            set {
                WillChangeValue ("Document");
                document = value;
                DidChangeValue ("Document");
            }
        }

        public ViewController ImageViewController {
            get { return ContentViewController as ViewController; }
        }

        public NSImage Image {
            get {
                return ImageViewController.Image;
            }
            set {
                ImageViewController.Image = value;
            }
        }
        #endregion

        #region Constructor
        public ImageWindow (IntPtr handle) : base (handle)
        {
        }
        #endregion

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

            // Create a new document instance
            Document = new ImageDocument ();

            // Attach to image view
            Document.ImageView = ImageViewController.ContentView;
        }
        #endregion

        #region Public Methods
        public void CopyImage (NSObject sender)
        {
            Document.CopyImage (sender);
        }

        public void PasteImage (NSObject sender)
        {
            Document.PasteImage (sender);
        }

        public void ImageOne (NSObject sender)
        {
            // Load image
            Image = NSImage.ImageNamed ("Image01.jpg");

            // Set image info
            Document.Info.Name = "city";
            Document.Info.ImageType = "jpg";
        }

        public void ImageTwo (NSObject sender)
        {
            // Load image
            Image = NSImage.ImageNamed ("Image02.jpg");

            // Set image info
            Document.Info.Name = "theater";
            Document.Info.ImageType = "jpg";
        }

        public void ImageThree (NSObject sender)
        {
            // Load image
            Image = NSImage.ImageNamed ("Image03.jpg");

            // Set image info
            Document.Info.Name = "keyboard";
            Document.Info.ImageType = "jpg";
        }

        public void ImageFour (NSObject sender)
        {
            // Load image
            Image = NSImage.ImageNamed ("Image04.jpg");

            // Set image info
            Document.Info.Name = "trees";
            Document.Info.ImageType = "jpg";
        }
        #endregion
    }
}

Echemos un vistazo a este código en detalle a continuación.

En primer lugar, exponemos una instancia de la clase ImageDocument que creamos anteriormente:

private ImageDocument _document;
...

[Export ("Document")]
public ImageDocument Document {
    get { return _document; }
    set {
        WillChangeValue ("Document");
        _document = value;
        DidChangeValue ("Document");
    }
}

Mediante Export, WillChangeValue y DidChangeValue, hemos configurado la propiedad Document para permitir la codificación de clave-valor y el enlace de datos en Xcode.

También exponemos la imagen de la imagen bien agregada a nuestra interfaz de usuario en Xcode con la siguiente propiedad:

public ViewController ImageViewController {
    get { return ContentViewController as ViewController; }
}

public NSImage Image {
    get {
        return ImageViewController.Image;
    }
    set {
        ImageViewController.Image = value;
    }
}

Cuando se carga y muestra la ventana principal, creamos una instancia de nuestra clase ImageDocument y adjuntamos bien la imagen de la interfaz de usuario con el código siguiente:

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

    // Create a new document instance
    Document = new ImageDocument ();

    // Attach to image view
    Document.ImageView = ImageViewController.ContentView;
}

Por último, en respuesta al usuario que hace clic en los elementos de la barra de herramientas copiar y pegar, llamamos a la instancia de la clase ImageDocument para realizar el trabajo real:

partial void CopyImage (NSObject sender) {
    Document.CopyImage(sender);
}

partial void PasteImage (Foundation.NSObject sender) {
    Document.PasteImage(sender);
}

Habilitación de los menús Archivo y Edición

Lo último que debemos hacer es habilitar el elemento de menú Nuevo desde el menú Archivo (para crear nuevas instancias de nuestra ventana principal) y habilitar el Cortar, Copiar y Pegar elementos de menú del Editar menú.

Para habilitar el elemento de menú Nuevo , edite el archivo AppDelegate.cs y agregue el código siguiente:

public int UntitledWindowCount { get; set;} =1;
...

[Export ("newDocument:")]
void NewDocument (NSObject sender) {
    // Get new window
    var storyboard = NSStoryboard.FromName ("Main", null);
    var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

    // Display
    controller.ShowWindow(this);

    // Set the title
    controller.Window.Title = (++UntitledWindowCount == 1) ? "untitled" : string.Format ("untitled {0}", UntitledWindowCount);
}

Para obtener más información, consulte la sección Trabajar con varias ventanas de nuestra documentación de Windows.

Para habilitar los elementos de menú Cortar, Copiar y Pegar elementos de menú, edite el archivo AppDelegate.cs y agregue el código siguiente:

[Export("copy:")]
void CopyImage (NSObject sender)
{
    // Get the main window
    var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

    // Anything to do?
    if (window == null)
        return;

    // Copy the image to the clipboard
    window.Document.CopyImage (sender);
}

[Export("cut:")]
void CutImage (NSObject sender)
{
    // Get the main window
    var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

    // Anything to do?
    if (window == null)
        return;

    // Copy the image to the clipboard
    window.Document.CopyImage (sender);

    // Clear the existing image
    window.Image = null;
}

[Export("paste:")]
void PasteImage (NSObject sender)
{
    // Get the main window
    var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

    // Anything to do?
    if (window == null)
        return;

    // Paste the image from the clipboard
    window.Document.PasteImage (sender);
}

Para cada elemento de menú, obtenemos la ventana actual, superior, clave y la convertimos a nuestraImageWindowclase:

var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

Desde allí llamamos a la instancia de clase ImageDocument de esa ventana para controlar las acciones de copia y pegado. Por ejemplo:

window.Document.CopyImage (sender);

Solo queremos Cortar, Copiar y Pegarelementos de menú que sean accesibles si hay datos de imagen en el panel de pegado predeterminado o en el pozo de imagen de la ventana activa actual.

Vamos a agregar un archivo EditMenuDelegate.cs al proyecto de Xamarin.Mac y convertirlo en similar al siguiente:

using System;
using AppKit;

namespace MacCopyPaste
{
    public class EditMenuDelegate : NSMenuDelegate
    {
        #region Override Methods
        public override void MenuWillHighlightItem (NSMenu menu, NSMenuItem item)
        {
        }

        public override void NeedsUpdate (NSMenu menu)
        {
            // Get list of menu items
            NSMenuItem[] Items = menu.ItemArray ();

            // Get the key window and determine if the required images are available
            var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;
            var hasImage = (window != null) && (window.Image != null);
            var hasImageOnPasteboard = (window != null) && window.Document.ImageAvailableOnPasteboard;

            // Process every item in the menu
            foreach(NSMenuItem item in Items) {
                // Take action based on the menu title
                switch (item.Title) {
                case "Cut":
                case "Copy":
                case "Delete":
                    // Only enable if there is an image in the view
                    item.Enabled = hasImage;
                    break;
                case "Paste":
                    // Only enable if there is an image on the pasteboard
                    item.Enabled = hasImageOnPasteboard;
                    break;
                default:
                    // Only enable the item if it has a sub menu
                    item.Enabled = item.HasSubmenu;
                    break;
                }
            }
        }
        #endregion
    }
}

De nuevo, obtenemos la ventana actual y superior y usamos su instancia de clase ImageDocument para ver si existen los datos de imagen necesarios. A continuación, usamos el método MenuWillHighlightItem para habilitar o deshabilitar cada elemento en función de este estado.

Edite el archivo AppDelegate.cs y haga que el método DidFinishLaunching tenga el siguiente aspecto:

public override void DidFinishLaunching (NSNotification notification)
{
    // Disable automatic item enabling on the Edit menu
    EditMenu.AutoEnablesItems = false;
    EditMenu.Delegate = new EditMenuDelegate ();
}

En primer lugar, deshabilitamos la habilitación automática y deshabilitación de elementos de menú en el menú Editar. A continuación, adjuntamos una instancia de la clase EditMenuDelegate que creamos anteriormente.

Para obtener más información, consulte nuestra documentación de Menús.

Probar la aplicación

Con todo en su lugar, estamos listos para probar la aplicación. Se muestra la compilación y ejecución de la aplicación y la interfaz principal:

Ejecución de la aplicación

Si abre el menú Edición, tenga en cuenta que Cortar, Copiar y Pegar están deshabilitados porque no hay ninguna imagen en la imagen bien o en el panel de pegado predeterminado:

Apertura del menú Editar

Si agrega una imagen al área de la imagen y vuelve a abrir el menú Editar, los elementos ahora se habilitarán:

Muestra de que los elementos del menú Edición están habilitados

Si copia la imagen y selecciona Nuevoen el menú archivo, puede pegar esa imagen en la nueva ventana:

Pegado de una imagen en una nueva ventana

En las secciones siguientes, echaremos un vistazo detallado al trabajo con el péguelo en una aplicación de Xamarin.Mac.

Acerca del área de montaje

En macOS (anteriormente conocido como OS X), el área de montaje (NSPasteboard) proporciona el soporte con varios procesos de servidor, como Copiar y Pegar, Arrastrar y Colocar y Servicios de aplicaciones. En las secciones siguientes, veremos más detenidamente varios conceptos del panel de pegado de claves.

¿Qué es un panel de pegado?

La NSPasteboard clase proporciona un mecanismo estandarizado para intercambiar información entre aplicaciones o dentro de una aplicación determinada. La función principal de un panel de pegado es para controlar las operaciones de copia y pegado:

  1. Cuando el usuario selecciona un elemento de una aplicación y usa el elemento de menú Cortar o Copiar, se colocan una o varias representaciones del elemento seleccionado en el panel de pegado.
  2. Cuando el usuario usa el elemento de menú Pegar (dentro de la misma aplicación o en otro diferente), la versión de los datos que puede controlar se copia del panel de pegado y se agrega a la aplicación.

Entre los usos menos obvios del panel de pegado se incluyen las operaciones de búsqueda, arrastrar, arrastrar y colocar y servicios de aplicaciones:

  • Cuando el usuario inicia una operación de arrastrar, los datos de arrastre se copian en el panel de pegado. Si la operación de arrastre finaliza con una colocación en otra aplicación, esa aplicación copia los datos del panel de pegado.
  • En el caso de los servicios de traducción, los datos que se van a traducir se copian en el panel de pegado mediante la aplicación solicitante. El servicio de aplicación, recupera los datos del panel de pegado, realiza la traducción y, a continuación, pega los datos de nuevo en el panel de pegado.

En su forma más sencilla, los paneles de pegado se usan para mover datos dentro de una aplicación determinada o entre aplicaciones y existen en un área de memoria global especial fuera del proceso de la aplicación. Aunque los conceptos del área de montaje son fáciles de entender, hay varios detalles más complejos que se deben tener en cuenta. Estos se tratarán en detalle a continuación.

Área de montaje con nombre

Un área de montaje puede ser público o privado y puede usarse con diversos propósitos dentro de una aplicación o entre varias aplicaciones. macOS proporciona varias área de montaje estándar, cada una con un uso específico y bien definido:

  • NSGeneralPboard - El área de montaje predeterminado para las operaciones Cortar, Copiar y Pegar.
  • NSRulerPboard - Admite las operaciones cortar, copiar y pegar en lasreglas.
  • NSFontPboard - Admite operaciones cortar, copiar y pegar en NSFont objetos.
  • NSFindPboard - Admite paneles de búsqueda específicos de la aplicación que pueden compartir texto de búsqueda.
  • NSDragPboard - Admite operaciones de arrastrar y colocar.

En la mayoría de las situaciones, usará uno de las área de montajes definidos por el sistema. Pero puede haber situaciones que requieran que cree sus propios pegadores. En estas situaciones, puede usar el FromName (string name) método de la NSPasteboard clase para crear un área de montaje personalizado con el nombre especificado.

Opcionalmente, puede llamar al método CreateWithUniqueName de la clase NSPasteboard para crear un panel de pegado con nombre único.

Elementos del panel de pegado

Cada fragmento de datos que una aplicación escribe en un panel de pegado se considera un Elemento de pegado y un panel de pegado puede contener varios elementos al mismo tiempo. De este modo, una aplicación puede escribir varias versiones de los datos que se copian en un panel de pegado (por ejemplo, texto sin formato y texto con formato) y la aplicación de recuperación solo puede leer los datos que puede procesar (por ejemplo, solo texto sin formato).

Representaciones de datos e identificadores de tipo uniforme

Normalmente, las operaciones del panel de pegado tienen lugar entre dos (o más) aplicaciones que no tienen conocimiento entre sí o los tipos de datos que cada uno puede controlar. Como se indicó en la sección anterior, para maximizar la posibilidad de compartir información, un panel de pegado puede contener varias representaciones de los datos que se copian y pegan.

Cada representación se identifica a través de un identificador de tipo uniforme (UTI), que no es más que una cadena simple que identifica de forma única el tipo de fecha que se presenta (para obtener más información, consulte la documentación de información General sobre identificadores uniformes de tipos uniformes de Apple).

Si va a crear un tipo de datos personalizado (por ejemplo, un objeto de dibujo en una aplicación de dibujo vectorial), puede crear su propio UTI para identificarlo de forma única en las operaciones de copia y pegado.

Cuando una aplicación se prepara para pegar los datos copiados de un área de montaje, debe encontrar la representación que mejor se adapte a sus capacidades (si existe alguna). Normalmente, este será el tipo más rico disponible (por ejemplo, texto con formato para una aplicación de procesamiento de texto), volviendo a los formularios más sencillos disponibles según sea necesario (texto sin formato para un editor de texto simple).

Datos prometidos

Por lo general, debe proporcionar tantas representaciones de los datos que se copian como sea posible para maximizar el uso compartido entre aplicaciones. Sin embargo, debido a las restricciones de tiempo o memoria, podría ser poco práctico escribir realmente cada tipo de datos en el panel de pegado.

En esta situación, puede colocar la primera representación de datos en el área de montaje y la aplicación receptora puede solicitar una representación diferente, que se puede generar sobre la marcha justo antes de la operación de pegado.

Al colocar el elemento inicial en el panel de pegado, especificará que una o varias de las otras representaciones disponibles se proporcionan mediante un objeto que se ajusta a la NSPasteboardItemDataProvider interfaz. Estos objetos proporcionarán las representaciones adicionales a petición, según lo solicitado por la aplicación receptora.

Recuento de cambios

Cada panel de pegado mantiene un recuento de cambios que aumenta cada vez que se declara un nuevo propietario. Una aplicación puede determinar si el contenido del panel de pegado ha cambiado desde la última vez que lo examinó comprobando el valor del recuento de cambios.

Use los métodos y ChangeCountde la ClearContents de la clase para modificar el NSPasteboard recuento de cambios de un área de montaje determinado.

Copiar datos en un área de montaje

Para realizar una operación de copia, primero se accede a un panel de pegado, se borra cualquier contenido existente y se escriben tantas representaciones de los datos como sea necesario en el área de montaje.

Por ejemplo:

// Get the standard pasteboard
var pasteboard = NSPasteboard.GeneralPasteboard;

// Empty the current contents
pasteboard.ClearContents();

// Add the current image to the pasteboard
pasteboard.WriteObjects (new NSImage[] {image});

Normalmente, solo escribirá en el pegado general, como hemos hecho en el ejemplo anterior. Cualquier objeto que envíe al WriteObjects método debe ajustarse a la INSPasteboardWriting interfaz. Varias clases integradas (como NSString, NSImage, NSURL, NSColor, NSAttributedStringy NSPasteboardItem) se ajustan automáticamente a esta interfaz.

Si va a escribir una clase de datos personalizada en el área de montaje, debe ajustarse a la INSPasteboardWriting interfaz o encapsularse en una instancia de la NSPasteboardItem clase (consulte la sección Tipos de datos personalizados a continuación).

Lectura de datos desde un panel de pegado

Como se indicó anteriormente, para maximizar la posibilidad de compartir datos entre aplicaciones, se pueden escribir varias representaciones de los datos copiados en el área de montaje. Es hasta la aplicación receptora seleccionar la versión más rica posible para sus funcionalidades (si existe alguna).

Operación de pegado simple

Los datos se leen desde el área de montaje mediante el ReadObjectsForClasses método. Necesitará dos parámetros:

  1. Matriz de tipos de NSObject clase basados que desea leer del área de montaje. Primero debe ordenar esto con el tipo de datos más deseado, con los tipos restantes en la preferencia decreciente.
  2. Diccionario que contiene restricciones adicionales (como limitar a tipos de contenido de dirección URL específicos) o un diccionario vacío si no se requieren restricciones adicionales.

El método devuelve una matriz de elementos que cumplen los criterios que hemos pasado y, por tanto, contiene como máximo el mismo número de tipos de datos que se solicitan. también es posible que ninguno de los tipos solicitados esté presente y se devolverá una matriz vacía.

Por ejemplo, el código siguiente comprueba si existe en NSImage el área de montaje general y lo muestra en una imagen bien si lo hace:

[Export("PasteImage:")]
public void PasteImage(NSObject sender) {

    // Initialize the pasteboard
    NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
    Class [] classArray  = { new Class ("NSImage") };

    bool ok = pasteboard.CanReadObjectForClasses (classArray, null);
    if (ok) {
        // Read the image off of the pasteboard
        NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray, null);
        NSImage image = (NSImage)objectsToPaste[0];

        // Display the new image
        ImageView.Image = image;
    }

    Class [] classArray2 = { new Class ("ImageInfo") };
    ok = pasteboard.CanReadObjectForClasses (classArray2, null);
    if (ok) {
        // Read the image off of the pasteboard
        NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray2, null);
        ImageInfo info = (ImageInfo)objectsToPaste[0];
            }
}

Solicitud de varios tipos de datos

En función del tipo de aplicación de Xamarin.Mac que se va a crear, puede controlar varias representaciones de los datos que se pegan. En esta situación, hay dos escenarios para recuperar datos del área de montaje:

  1. Realice una sola llamada al ReadObjectsForClasses método y proporcione una matriz de todas las representaciones que desee (en el orden preferido).
  2. Realice varias llamadas al ReadObjectsForClasses método solicitando una matriz de tipos diferente cada vez.

Consulte la sección Operación de pegado simple anterior para obtener más información sobre cómo recuperar datos de un panel de pegado.

Comprobación de los tipos de datos existentes

Hay veces que es posible que desee comprobar si un panel de pegado contiene una representación de datos determinada sin leer realmente los datos del área de montaje (por ejemplo, habilitar el elemento de menú Pegar solo cuando existen datos válidos).

Llame al CanReadObjectForClasses método del área de montaje para ver si contiene un tipo determinado.

Por ejemplo, el código siguiente determina si el área de montaje general contiene una NSImage instancia:

public bool ImageAvailableOnPasteboard {
    get {
        // Initialize the pasteboard
        NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
        Class [] classArray  = { new Class ("NSImage") };

        // Check to see if an image is on the pasteboard
        return pasteboard.CanReadObjectForClasses (classArray, null);
    }
}

Lectura de direcciones URL desde el área de montaje

En función de la función de una aplicación de Xamarin.Mac determinada, es posible que sea necesario leer las direcciones URL de un panel de pegado, pero solo si cumplen un conjunto determinado de criterios (como apuntar a archivos o direcciones URL de un tipo de datos específico). En esta situación, puede especificar criterios de búsqueda adicionales mediante el segundo parámetro de los CanReadObjectForClasses métodos o ReadObjectsForClasses.

Tipos de datos personalizados

Hay ocasiones en las que tendrá que guardar sus propios tipos personalizados en el área de montaje desde una aplicación de Xamarin.Mac. Por ejemplo, una aplicación de dibujo vectorial que permite al usuario copiar y pegar objetos de dibujo.

En esta situación, deberá diseñar la clase personalizada de datos para que herede de NSObject y se ajuste a algunas interfaces (INSCodingy INSPasteboardWritingINSPasteboardReading). Opcionalmente, puede usar para NSPasteboardItem encapsular los datos que se van a copiar o pegar.

Ambas opciones se tratarán en detalle a continuación.

Uso de una clase personalizada

En esta sección se expandirá la aplicación de ejemplo simple que creamos al principio de este documento y agregaremos una clase personalizada para realizar un seguimiento de la información sobre la imagen que estamos copiando y pegando entre ventanas.

Agregue una nueva clase al proyecto y llámela ImageInfo.cs. Edite el archivo y haga que tenga un aspecto similar al siguiente:

using System;
using AppKit;
using Foundation;

namespace MacCopyPaste
{
    [Register("ImageInfo")]
    public class ImageInfo : NSObject, INSCoding, INSPasteboardWriting, INSPasteboardReading
    {
        #region Computed Properties
        [Export("name")]
        public string Name { get; set; }

        [Export("imageType")]
        public string ImageType { get; set; }
        #endregion

        #region Constructors
        [Export ("init")]
        public ImageInfo ()
        {
        }

        public ImageInfo (IntPtr p) : base (p)
        {
        }

        [Export ("initWithCoder:")]
        public ImageInfo(NSCoder decoder) {

            // Decode data
            NSString name = decoder.DecodeObject("name") as NSString;
            NSString type = decoder.DecodeObject("imageType") as NSString;

            // Save data
            Name = name.ToString();
            ImageType = type.ToString ();
        }
        #endregion

        #region Public Methods
        [Export ("encodeWithCoder:")]
        public void EncodeTo (NSCoder encoder) {

            // Encode data
            encoder.Encode(new NSString(Name),"name");
            encoder.Encode(new NSString(ImageType),"imageType");
        }

        [Export ("writableTypesForPasteboard:")]
        public virtual string[] GetWritableTypesForPasteboard (NSPasteboard pasteboard) {
            string[] writableTypes = {"com.xamarin.image-info", "public.text"};
            return writableTypes;
        }

        [Export ("pasteboardPropertyListForType:")]
        public virtual NSObject GetPasteboardPropertyListForType (string type) {

            // Take action based on the requested type
            switch (type) {
            case "com.xamarin.image-info":
                return NSKeyedArchiver.ArchivedDataWithRootObject(this);
            case "public.text":
                return new NSString(string.Format("{0}.{1}", Name, ImageType));
            }

            // Failure, return null
            return null;
        }

        [Export ("readableTypesForPasteboard:")]
        public static string[] GetReadableTypesForPasteboard (NSPasteboard pasteboard){
            string[] readableTypes = {"com.xamarin.image-info", "public.text"};
            return readableTypes;
        }

        [Export ("readingOptionsForType:pasteboard:")]
        public static NSPasteboardReadingOptions GetReadingOptionsForType (string type, NSPasteboard pasteboard) {

            // Take action based on the requested type
            switch (type) {
            case "com.xamarin.image-info":
                return NSPasteboardReadingOptions.AsKeyedArchive;
            case "public.text":
                return NSPasteboardReadingOptions.AsString;
            }

            // Default to property list
            return NSPasteboardReadingOptions.AsPropertyList;
        }

        [Export ("initWithPasteboardPropertyList:ofType:")]
        public NSObject InitWithPasteboardPropertyList (NSObject propertyList, string type) {

            // Take action based on the requested type
            switch (type) {
            case "com.xamarin.image-info":
                return new ImageInfo();
            case "public.text":
                return new ImageInfo();
            }

            // Failure, return null
            return null;
        }
        #endregion
    }
}

En las secciones siguientes, echaremos un vistazo detallado a esta clase.

Herencia e interfaces

Antes de que se pueda escribir o leer una clase de datos personalizada desde un panel de pegado, debe ajustarse a las INSPastebaordWriting interfaces y INSPasteboardReading. Además, debe heredar de NSObject y también cumplir con la INSCoding interfaz:

[Register("ImageInfo")]
public class ImageInfo : NSObject, INSCoding, INSPasteboardWriting, INSPasteboardReading
...

La clase también debe exponerse al Objective-C uso de la Register directiva y debe exponer las propiedades o métodos necesarios mediante Export. Por ejemplo:

[Export("name")]
public string Name { get; set; }

[Export("imageType")]
public string ImageType { get; set; }

Estamos exponiendo los dos campos de datos que esta clase contendrá: el nombre de la imagen y su tipo (jpg, png, etc.).

Para obtener más información, consulte la sección Exposición de clases o métodos de C# para Objective-C de la documentación de Xamarin.Mac Internals, explica los atributos Register y Export usados para conectar las clases de C# a Objective-C objetos y elementos de la interfaz de usuario.

Constructores

Se requerirán dos constructores (expuestos correctamente a Objective-C) para nuestra clase de datos personalizada para que se pueda leer desde un área de montaje:

[Export ("init")]
public ImageInfo ()
{
}

[Export ("initWithCoder:")]
public ImageInfo(NSCoder decoder) {

    // Decode data
    NSString name = decoder.DecodeObject("name") as NSString;
    NSString type = decoder.DecodeObject("imageType") as NSString;

    // Save data
    Name = name.ToString();
    ImageType = type.ToString ();
}

En primer lugar, exponemos el constructor vacío bajo el método predeterminado Objective-C de init.

A continuación, se expone un NSCoding constructor compatible que se usará para crear una nueva instancia del objeto desde el área de montaje al pegar en el nombre exportado de initWithCoder.

Este constructor toma NSCoder (tal como lo crea un NSKeyedArchiver cuando se escribe en el panel de pegado), extrae los datos emparejados de clave-valor y lo guarda en los campos de propiedad de la clase de datos.

Escribir en el área de montaje

Al ajustarse a la INSPasteboardWriting interfaz, es necesario exponer dos métodos y, opcionalmente, un tercer método, para que la clase se pueda escribir en el área de montaje.

En primer lugar, es necesario indicar al área de montaje qué representaciones de tipo de datos se pueden escribir en la clase personalizada:

[Export ("writableTypesForPasteboard:")]
public virtual string[] GetWritableTypesForPasteboard (NSPasteboard pasteboard) {
    string[] writableTypes = {"com.xamarin.image-info", "public.text"};
    return writableTypes;
}

Cada representación se identifica a través de un identificador uniforme de tipo (UTI), que no es más que una cadena simple que identifica de forma única el tipo de datos que se presentan (para obtener más información, consulte la documentación de información general sobre identificadores uniformes de tipos uniformes de Apple ).

Para nuestro formato personalizado, estamos creando nuestra propia UTI: "com.xamarin.image-info" (tenga en cuenta que está en notación inversa como un identificador de aplicación). Nuestra clase también es capaz de escribir una cadena estándar en el área de montaje (public.text).

A continuación, es necesario crear el objeto en el formato solicitado que realmente se escribe en el área de montaje:

[Export ("pasteboardPropertyListForType:")]
public virtual NSObject GetPasteboardPropertyListForType (string type) {

    // Take action based on the requested type
    switch (type) {
    case "com.xamarin.image-info":
        return NSKeyedArchiver.ArchivedDataWithRootObject(this);
    case "public.text":
        return new NSString(string.Format("{0}.{1}", Name, ImageType));
    }

    // Failure, return null
    return null;
}

Para el tipo de public.text, se devuelve un objeto NSString simple y con formato. Para el tipo personalizado com.xamarin.image-info, usamos y NSKeyedArchiver la NSCoder interfaz para codificar la clase de datos personalizada en un archivo emparejado clave-valor. Tendremos que implementar el método siguiente para controlar realmente la codificación:

[Export ("encodeWithCoder:")]
public void EncodeTo (NSCoder encoder) {

    // Encode data
    encoder.Encode(new NSString(Name),"name");
    encoder.Encode(new NSString(ImageType),"imageType");
}

Los pares clave-valor individuales se escriben en el codificador y se descodificarán mediante el segundo constructor que hemos agregado anteriormente.

Opcionalmente, podemos incluir el método siguiente para definir cualquier opción al escribir datos en el área de montaje:

[Export ("writingOptionsForType:pasteboard:"), CompilerGenerated]
public virtual NSPasteboardWritingOptions GetWritingOptionsForType (string type, NSPasteboard pasteboard) {
    return NSPasteboardWritingOptions.WritingPromised;
}

Actualmente solo está disponible la opción WritingPromised y debe usarse cuando solo se promete un tipo determinado y no se escribe realmente en el panel de pegado. Para obtener más información, consulte la secciónDatos prometidos anterior.

Con estos métodos implementados, se puede usar el código siguiente para escribir nuestra clase personalizada en el área de montaje:

// Get the standard pasteboard
var pasteboard = NSPasteboard.GeneralPasteboard;

// Empty the current contents
pasteboard.ClearContents();

// Add info to the pasteboard
pasteboard.WriteObjects (new ImageInfo[] { Info });

Lectura desde el área de montaje

Al ajustarse a la INSPasteboardReading interfaz, es necesario exponer tres métodos para que la clase de datos personalizada se pueda leer desde el área de montaje.

En primer lugar, es necesario indicar al área de montaje qué representaciones de tipo de datos puede leer la clase personalizada del Portapapeles:

[Export ("readableTypesForPasteboard:")]
public static string[] GetReadableTypesForPasteboard (NSPasteboard pasteboard){
    string[] readableTypes = {"com.xamarin.image-info", "public.text"};
    return readableTypes;
}

De nuevo, estos se definen como UTIs simples y son los mismos tipos que definimos en la sección Escritura del área de montaje anterior.

A continuación, es necesario indicar el área de montaje cómo se leerá cada uno de los tipos de UTI mediante el método siguiente:

[Export ("readingOptionsForType:pasteboard:")]
public static NSPasteboardReadingOptions GetReadingOptionsForType (string type, NSPasteboard pasteboard) {

    // Take action based on the requested type
    switch (type) {
    case "com.xamarin.image-info":
        return NSPasteboardReadingOptions.AsKeyedArchive;
    case "public.text":
        return NSPasteboardReadingOptions.AsString;
    }

    // Default to property list
    return NSPasteboardReadingOptions.AsPropertyList;
}

Para el tipo de com.xamarin.image-info, se indica al panel de pegado que descodifique el par clave-valor que creamos con el NSKeyedArchiver al escribir la clase en el área de montaje llamando al constructor initWithCoder: que agregamos a la clase .

Por último, es necesario agregar el método siguiente para leer las otras representaciones de datos UTI desde el panel de pegado:

[Export ("initWithPasteboardPropertyList:ofType:")]
public NSObject InitWithPasteboardPropertyList (NSObject propertyList, string type) {

    // Take action based on the requested type
    switch (type) {
    case "public.text":
        return new ImageInfo();
    }

    // Failure, return null
    return null;
}

Con todos estos métodos implementados, la clase de datos personalizada se puede leer desde el panel de pegado mediante el código siguiente:

// Initialize the pasteboard
NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
var classArrayPtrs = new [] { Class.GetHandle (typeof(ImageInfo)) };
NSArray classArray = NSArray.FromIntPtrs (classArrayPtrs);

// NOTE: Sending messages directly to the base Objective-C API because of this defect:
// https://bugzilla.xamarin.com/show_bug.cgi?id=31760
// Check to see if image info is on the pasteboard
ok = bool_objc_msgSend_IntPtr_IntPtr (pasteboard.Handle, Selector.GetHandle ("canReadObjectForClasses:options:"), classArray.Handle, IntPtr.Zero);

if (ok) {
    // Read the image off of the pasteboard
    NSObject [] objectsToPaste = NSArray.ArrayFromHandle<Foundation.NSObject>(IntPtr_objc_msgSend_IntPtr_IntPtr (pasteboard.Handle, Selector.GetHandle ("readObjectsForClasses:options:"), classArray.Handle, IntPtr.Zero));
    ImageInfo info = (ImageInfo)objectsToPaste[0];
}

Uso de un objeto NSPasteboardItem

Puede haber ocasiones en las que necesite escribir elementos personalizados en el área de montaje que no garantizan la creación de una clase personalizada o si desea proporcionar datos en un formato común, solo según sea necesario. En estas situaciones, puede usar un NSPasteboardItem.

Un NSPasteboardItem proporciona un control específico sobre los datos que se escriben en el área de montaje y están diseñados para el acceso temporal; se deben eliminar después de que se haya escrito en el pegado.

Escritura de datos

Para escribir los datos personalizados en, NSPasteboardItem deberá proporcionar un personalizado NSPasteboardItemDataProvider. Agregue una nueva clase al proyecto y llámela ImageInfoDataProvider.cs. Edite el archivo y haga que tenga un aspecto similar al siguiente:

using System;
using AppKit;
using Foundation;

namespace MacCopyPaste
{
    [Register("ImageInfoDataProvider")]
    public class ImageInfoDataProvider : NSPasteboardItemDataProvider
    {
        #region Computed Properties
        public string Name { get; set;}
        public string ImageType { get; set;}
        #endregion

        #region Constructors
        [Export ("init")]
        public ImageInfoDataProvider ()
        {
        }

        public ImageInfoDataProvider (string name, string imageType)
        {
            // Initialize
            this.Name = name;
            this.ImageType = imageType;
        }

        protected ImageInfoDataProvider (NSObjectFlag t){
        }

        protected internal ImageInfoDataProvider (IntPtr handle){

        }
        #endregion

        #region Override Methods
        [Export ("pasteboardFinishedWithDataProvider:")]
        public override void FinishedWithDataProvider (NSPasteboard pasteboard)
        {

        }

        [Export ("pasteboard:item:provideDataForType:")]
        public override void ProvideDataForType (NSPasteboard pasteboard, NSPasteboardItem item, string type)
        {

            // Take action based on the type
            switch (type) {
            case "public.text":
                // Encode the data to string
                item.SetStringForType(string.Format("{0}.{1}", Name, ImageType),type);
                break;
            }

        }
        #endregion
    }
}

Como hicimos con la clase de datos personalizada, es necesario usar las directivas Register y Export para exponerla a Objective-C. La clase debe heredar de NSPasteboardItemDataProvider y debe implementar los FinishedWithDataProvider métodos y ProvideDataForType.

Use el método ProvideDataForType para proporcionar los datos que se ajustarán en el NSPasteboardItem de la siguiente manera:

[Export ("pasteboard:item:provideDataForType:")]
public override void ProvideDataForType (NSPasteboard pasteboard, NSPasteboardItem item, string type)
{

    // Take action based on the type
    switch (type) {
    case "public.text":
        // Encode the data to string
        item.SetStringForType(string.Format("{0}.{1}", Name, ImageType),type);
        break;
    }

}

En este caso, almacenamos dos fragmentos de información sobre nuestra imagen (Name e ImageType) y los escribiremos en una cadena simple (public.text).

Escriba los datos en el área de montaje y use el código siguiente:

// Get the standard pasteboard
var pasteboard = NSPasteboard.GeneralPasteboard;

// Using a Pasteboard Item
NSPasteboardItem item = new NSPasteboardItem();
string[] writableTypes = {"public.text"};

// Add a data provider to the item
ImageInfoDataProvider dataProvider = new ImageInfoDataProvider (Info.Name, Info.ImageType);
var ok = item.SetDataProviderForTypes (dataProvider, writableTypes);

// Save to pasteboard
if (ok) {
    pasteboard.WriteObjects (new NSPasteboardItem[] { item });
}

Lectura de datos

Para volver a leer los datos desde el área de montaje, use el código siguiente:

// Initialize the pasteboard
NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
Class [] classArray  = { new Class ("NSImage") };

bool ok = pasteboard.CanReadObjectForClasses (classArray, null);
if (ok) {
    // Read the image off of the pasteboard
    NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray, null);
    NSImage image = (NSImage)objectsToPaste[0];

    // Do something with data
    ...
}

Class [] classArray2 = { new Class ("ImageInfo") };
ok = pasteboard.CanReadObjectForClasses (classArray2, null);
if (ok) {
    // Read the image off of the pasteboard
    NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray2, null);

    // Do something with data
    ...
}

Resumen

En este artículo se ha tomado un vistazo detallado al trabajo con el área de montaje en una aplicación de Xamarin.Mac para admitir operaciones de copia y pegado. En primer lugar, introdujo un ejemplo sencillo para familiarizarse con las operaciones estándar de las áreas de montaje. A continuación, se tomó un vistazo detallado del área de montaje y cómo leer y escribir datos de él. Por último, se ha examinado el uso de un tipo de datos personalizado para admitir la copia y el pegado de tipos de datos complejos dentro de una aplicación.