Copiar y pegar en Xamarin.Mac

En este artículo se describe cómo trabajar con la placa de pegado para proporcionar 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 a la misma compatibilidad de pegar (copiar y pegar) que tiene un desarrollador que trabaja Objective-C en .

En este artículo se tratarán las dos formas principales de usar la placa de pegar en una aplicación de Xamarin.Mac:

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

Ejemplo de la aplicación en ejecuciónEjemplo de la aplicación en

En este artículo, se tratarán los conceptos básicos de trabajar con el pasteboard en una aplicación de Xamarin.Mac para admitir las operaciones de copiar y pegar. Se recomienda encarecidamente trabajar primero en el artículo Hola, Mac, en concreto en las secciones Introducción a Xcode y Interface Builder y Salidas y acciones, ya que trata los conceptos y técnicas clave que se usarán en este artículo.

Es posible que también quiera echar un vistazo a la sección del documento Aspectos internos de Exposing C# classes / methods to Objective-CExposing C# classes / methods to Objective-C donde se explican los atributos y que se usan para conectar las clases de C# a objetos y elementos de la interfaz RegisterExport de Objective-C usuario.

Introducción a la placa de pegar

La placa de pegar presenta un mecanismo estandarizado para intercambiar datos dentro de una aplicación determinada o entre aplicaciones. El uso típico de una placa de pegar en una aplicación de Xamarin.Mac es controlar las operaciones de copiar y pegar, pero también se admiten otras operaciones (como Arrastrar drop y & Servicios de aplicación).

Para empezar a trabajar rápidamente, comenzaremos con una introducción sencilla y práctica al uso de tablas de pegar en una aplicación de Xamarin.Mac. Más adelante, proporcionaremos una explicación detallada de cómo funciona la placa de pegar 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 desde o hacia 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 compatibilidad con copiar y pegar.

Haga lo siguiente:

  1. Inicie Visual Studio para Mac haga clic en el vínculo Project... .

  2. Seleccione Aplicación MacCocoaApp yhaga clic en el botón Siguiente:

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

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

    Establecer el nombre del proyecto Establecerel nombre del

  4. Haga clic en el botón Crear:

    Confirmación de la nueva configuración del proyectoConfirmación de la nueva configuración del

Agregar un NSDocument

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

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

Agregar un NSDocument al proyecto

Escriba ImageDocument en Nombre ImageDocument haga clic en el botón Nuevo. Edite la clase ImageDocument.cs y haga que se parezca a la 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 parte del código en detalle a continuación.

El código siguiente proporciona una propiedad para comprobar la existencia de datos de imagen en la placa de pegar predeterminada; si hay una imagen disponible, se true devuelve 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 pegar 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 panel de pegado 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 y configúrelas de la siguiente manera:

Edición de la barra de herramientas

Agregue un elemento de la barra de herramientas de imagen para copiar y pegar en el lado izquierdo de la barra de herramientas. Los usaremos como accesos directos para copiar y pegar desde el menú Editar. A continuación, agregue cuatro elementos de la barra de herramientas de imagen al lado derecho de la barra de herramientas. Los usaremos para rellenar bien la imagen 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 la imagen:

Creación de salidas y acciones Creación

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 la 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 en el Panel de solución y haga que sea parecido al 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 ImageDocument clase que creamos anteriormente:

private ImageDocument _document;
...

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

Mediante , y , hemos configurado la propiedad para permitir Key-Value codificación y enlace ExportWillChangeValue de datos en DidChangeValueDocument Xcode.

También exponemos la imagen de la imagen que agregamos a la 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 se muestra la ventana principal, creamos una instancia de nuestra clase y le adjuntamos la imagen de la interfaz de usuario ImageDocument 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 de 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);
}

Habilitar los menús Archivo y Editar

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

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 Working with Multiple Windows (Trabajar con varias Windows de nuestra documentación Windows datos).

Para habilitar los elementos demenú Cortar, Copiar y Pegar, 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ú, se obtiene la ventana de clave actual, superior y se convierte a nuestra ImageWindow clase:

var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

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

window.Document.CopyImage (sender);

Solo queremos que los elementosde menú Cortar, Copiar y Pegar sean accesibles si hay datos de imagen en el panel de pegar predeterminado o en el cuadro de imágenes de la ventana activa actual.

Vamos a agregar un archivo EditMenuDelegate.cs al proyecto de Xamarin.Mac y hacer que sea parecido 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, se obtiene la ventana de nivel superior actual y se usa su instancia de clase ImageDocument para ver si existen los datos de imagen necesarios. A continuación, MenuWillHighlightItem usamos el método para habilitar o deshabilitar cada elemento en función de este estado.

Edite el archivo AppDelegate.cs y haga que el método sea parecido al siguiente:

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

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

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

Probar la aplicación

Con todo listo, estamos listos para probar la aplicación. Compile y ejecute la aplicación y se muestra la interfaz principal:

Ejecución de la aplicación

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

Abrir el menú Editar Abrirel menú

Si agrega una imagen a la imagen y vuelve a abrir el menú Editar, los elementos ahora estarán habilitados:

Mostrar que los elementos de menú Editar están habilitados

Si copia la imagen y selecciona Nuevo en el menú archivo, puede pegarla en la nueva ventana:

Pegar una imagen en una nueva ventana Pegaruna imagen en una nueva

En las secciones siguientes, echaremos un vistazo detallado al trabajo con la placa de pegar en una aplicación de Xamarin.Mac.

Acerca de la placa de pegar

En macOS (anteriormente conocido como OS X), la placa de pegado ( ) proporciona compatibilidad con varios procesos de servidor, como Copiar pegar, Arrastrar colocar y NSPasteboard& Servicios de & aplicación. En las secciones siguientes, echaremos un vistazo más de cerca a varios conceptos clave del tablero de pegar.

¿Qué es un tablero de pegar?

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 una placa de pegado es controlar las operaciones de copiar y pegar:

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

Los usos menos obvios del tablero de pegar incluyen operaciones de búsqueda, arrastrar, arrastrar y colocar, y servicios de aplicación:

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

En su forma más sencilla, las tablas de pegar se usan para mover datos dentro de una aplicación determinada o entre aplicaciones y, por tanto, existen en un área de memoria global especial fuera del proceso de la aplicación. Aunque los conceptos de los tableros de pegar son fáciles de comprender, hay varios detalles más complejos que deben tenerse en cuenta. Se tratarán con detalle a continuación.

Tablas de pegar con nombre

Una placa de pegar puede ser pública o privada y se puede usar para diversos propósitos dentro de una aplicación o entre varias aplicaciones. macOS proporciona varias tablas de pegar estándar, cada una con un uso específico y bien definido:

  • NSGeneralPboard - El pasteboard predeterminado para NSGeneralPboardCopiary Pegar.
  • NSRulerPboard: admite NSRulerPboardcopiar y pegar en las reglas.
  • NSFontPboard : admite NSFontPboardcopiary pegar en objetos.
  • NSFindPboard : admite paneles de búsqueda específicos de la aplicación que pueden compartir texto de búsqueda.
  • NSDragPboard : admite NSDragPboard colocar.

En la mayoría de las situaciones, usará una de las tablas de pegar definidas por el sistema. Sin embargo, puede haber situaciones que requieran que cree sus propias tablas de pegar. En estas situaciones, puede usar el método de la clase para crear una placa FromName (string name) de pegar personalizada con el nombre NSPasteboard especificado.

Opcionalmente, puede llamar al método CreateWithUniqueName de la clase para crear una placa de pegar denominada de forma NSPasteboard única.

Elementos de la placa de pegado

Cada fragmento de datos que una aplicación escribe en una placa de pegado se considera un elemento de la placa de pegado y una placa puede contener varios elementos al mismo tiempo. De esta manera, una aplicación puede escribir varias versiones de los datos que se copian en una placa de pegar (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 el texto sin formato).

Representaciones de datos e identificadores de tipo uniforme

Las operaciones de pegado suelen tener lugar entre dos (o más) aplicaciones que no tienen conocimientos entre sí o los tipos de datos que cada una puede controlar. Como se indicó en la sección anterior, para maximizar la posibilidad de compartir información, una placa 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 uniforme de tipo (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 introducción a los identificadores uniformes de tipos 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 copiar y pegar.

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

Datos comprometidos

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, puede que no sea práctico escribir realmente cada tipo de datos en el tablero de pegar.

En esta situación, puede colocar la primera representación de datos en el tablero de pegar 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 pegar, especificará que una o varias de las otras representaciones disponibles las proporciona 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 mesa de trabajo mantiene un recuento de cambios que se incrementa 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 la examinaba comprobando el valor del recuento de cambios.

Use los métodos y de la clase para modificar el recuento de ChangeCount cambios de un panel de pegado ClearContentsNSPasteboard determinado.

Copia de datos en un pasteboard

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 se requieran en el panel de pegado.

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 pasteboard general, como hemos hecho en el ejemplo anterior. Cualquier objeto que envíe al WriteObjects método debe ajustarse a la interfaz WriteObjectsINSPasteboardWriting . Varias clases integradas (como , , , , y ) se ajustan NSStringNSImage automáticamente a esta NSURLNSColorNSAttributedStringNSPasteboardItem interfaz.

Si va a escribir una clase de datos personalizada en el tablero de pegar, debe ajustarse a la interfaz o estar encapsulada en una instancia de la clase (consulte la sección Tipos de datos personalizados a INSPasteboardWritingNSPasteboardItem continuación). INSPasteboardWriting

Lectura de datos de un panel de pegar

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

Operación de pegado simple

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

  1. Matriz de NSObject tipos de clase basados que desea leer desde el tablero de pegar. Debe ordenarlo primero con el tipo de datos más deseado, con los tipos restantes en preferencia decreciente.
  2. Diccionario que contiene restricciones adicionales (por ejemplo, la limitación a tipos de contenido de dirección URL específicos) o un diccionario vacío si no se requieren más restricciones.

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 devuelva una matriz vacía.

Por ejemplo, el código siguiente comprueba si existe un elemento en el panel de pegado general y lo muestra en una imagen bien NSImage 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 de la placa de pegado:

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

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

Comprobación de los tipos de datos existentes

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

Llame al CanReadObjectForClasses método de la placa de pegado para ver si contiene un tipo determinado.

Por ejemplo, el código siguiente determina si la placa de pegado general contiene una NSImage instancia de :

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 panel de pegado

En función de la función de una aplicación de Xamarin.Mac determinada, puede ser necesario leer las direcciones URL de una placa de pegar, 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 CanReadObjectForClassesReadObjectsForClasses métodos o .

Tipos de datos personalizados

Hay ocasiones en las que tendrá que guardar sus propios tipos personalizados en el pasteboard 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 y se ajuste a algunas NSObject interfaces ( INSCoding , y INSPasteboardWritingINSPasteboardReading ). Opcionalmente, puede usar para encapsular los datos que se van a NSPasteboardItem copiar o pegar.

Estas dos opciones se tratarán con detalle a continuación.

Uso de una clase personalizada

En esta sección, ampliaremos la sencilla aplicación de ejemplo 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 sea parecido 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 se va a echar un vistazo detallado a esta clase.

Herencia e interfaces

Antes de que una clase de datos personalizada se pueda escribir o leer desde una placa de pegar, debe ajustarse a las INSPastebaordWritingINSPasteboardReading interfaces y . Además, debe heredar de y NSObject también ajustarse a la INSCoding interfaz :

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

La clase también debe exponerse a mediante la directiva y debe exponer las propiedades o métodos Objective-CRegister 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 contendrá esta clase: el nombre de la imagen y su tipo (jpg, png, etc.).

Para obtener más información, consulte la sección de la documentación sobre los elementos internos de Exposing C# classes / methods to Objective-CExposing C# classes / methods to Objective-C donde se explican los atributos y que se usan para conectar las clases de C# a objetos y elementos de Register la interfaz de ExportObjective-C usuario.

Constructores

Se necesitan dos constructores (expuestos correctamente a ) para nuestra clase de datos personalizados para que se puedan leer Objective-C desde una placa de pegar:

[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 en el método predeterminado de init .

A continuación, exponemos un constructor compatible que se usará para crear una nueva instancia del objeto desde la placa de pegado al pegar bajo el NSCoding nombre exportado de initWithCoder .

Este constructor toma un objeto (tal y como lo creó cuando se escribe en el pegar), extrae los datos emparejados clave-valor y los guarda en los campos de propiedad de la NSCoderNSKeyedArchiver clase de datos.

Escritura en el tablero de pegar

Al ajustarnos a la interfaz , es necesario exponer dos métodos y, opcionalmente, un tercer método, para que la clase se pueda INSPasteboardWriting escribir en el pegar.

En primer lugar, es necesario decir a la mesa de trabajo en qué representaciones de tipo de datos se puede escribir 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 de identificadores uniformes de tipos de Apple).

Para nuestro formato personalizado, estamos creando nuestro propio 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 la placa de pegado ( public.text ).

A continuación, es necesario crear el objeto en el formato solicitado que realmente se escribe en la mesa de trabajo:

[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 public.text tipo , vamos a devolver un objeto simple con NSString formato. Para el tipo personalizado, se usa y la interfaz para codificar la clase de datos personalizada en un com.xamarin.image-infoNSKeyedArchiver archivo NSCoder 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 agregamos anteriormente.

Opcionalmente, podemos incluir el método siguiente para definir cualquier opción al escribir datos en la mesa de trabajo:

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

Actualmente solo está disponible la opción y debe usarse cuando un tipo determinado solo se ha comprometido y no se ha escrito WritingPromised realmente en la placa de pegado. Para obtener más información, consulte la sección Datos comprometidos anterior.

Con estos métodos en su lugar, se puede usar el código siguiente para escribir nuestra clase personalizada en la mesa de trabajo:

// 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 panel de pegado

Al ajustarnos a la interfaz , es necesario exponer tres métodos para que la clase de datos personalizada se pueda INSPasteboardReading leer desde la placa de pegado.

En primer lugar, es necesario decir a la mesa de trabajo qué representaciones de tipo de datos que la clase personalizada puede leer desde el Portapapeles:

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

Una vez más, se definen como UTI simples y son los mismos tipos que definimos en la sección Escribir en el tablero de pegado anterior.

A continuación, es necesario decir a la placa de pegado 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 , se le pide a la placa de pegado que descodifique el par clave-valor que creamos con al escribir la clase en la placa de pegado llamando al constructor que agregamos a la com.xamarin.image-infoNSKeyedArchiver clase initWithCoder: .

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

[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 en su lugar, 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 NSPasteboardItem

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

Proporciona un control más preciso sobre los datos que se escriben en el panel de pegado y está diseñado para el acceso temporal; debe eliminarse después de que se haya escrito en el panel NSPasteboardItem de pegado.

Escritura de datos

Para escribir los datos personalizados en NSPasteboardItem un , deberá proporcionar un NSPasteboardItemDataProvider personalizado. Agregue una nueva clase al proyecto y llámela ImageInfoDataProvider.cs. Edite el archivo y haga que sea parecido 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, necesitamos usar las directivas Register y Export para exponerla a Objective-C . La clase debe heredar de NSPasteboardItemDataProvider y debe implementar los FinishedWithDataProviderProvideDataForType métodos y .

Use el ProvideDataForType método para proporcionar los datos que se encapsularán en el como se muestra a NSPasteboardItem continuación:

[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 escriba los datos en la mesa de trabajo 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 de la placa de pegado, 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 realizado un análisis detallado del trabajo con la placa de pegado en una aplicación de Xamarin.Mac para admitir las operaciones de copiar y pegar. En primer lugar, introdujo un ejemplo sencillo para familiarizarse con las operaciones de las tablas de pegado estándar. A continuación, se ha tomado una vista detallada de la placa de pegado y cómo leer y escribir datos de ella. Por último, se ha visto cómo usar un tipo de datos personalizado para admitir la copia y el pegar de tipos de datos complejos dentro de una aplicación.