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:
- 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.
- 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.
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:
Inicie Visual Studio para Mac haga clic en el vínculo Project... .
Seleccione Aplicación MacCocoaApp yhaga clic en el botón Siguiente:
Escriba
MacCopyPastepara el nombre ProjectMacCopyPastemantenga todo lo demás como predeterminado. Haga clic en Siguiente:Haga clic en el botón Crear:
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.:

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

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:
el menú
Si agrega una imagen a la imagen y vuelve a abrir el menú Editar, los elementos ahora estarán habilitados:

Si copia la imagen y selecciona Nuevo en el menú archivo, puede pegarla en la nueva ventana:
una 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:
- 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.
- 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 paraNSGeneralPboardCopiary Pegar.NSRulerPboard: admiteNSRulerPboardcopiar y pegar en las reglas.NSFontPboard: admiteNSFontPboardcopiary pegar en objetos.NSFindPboard: admite paneles de búsqueda específicos de la aplicación que pueden compartir texto de búsqueda.NSDragPboard: admiteNSDragPboardcolocar.
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:
- Matriz de
NSObjecttipos 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. - 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:
- Realice una sola llamada al método y proporcione una matriz de todas las representaciones que
ReadObjectsForClassesdesee (en el orden preferido). - Realice varias llamadas al
ReadObjectsForClassesmé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.
Ejemplo de la aplicación en
Creación de un nuevo proyecto de aplicación de
el nombre del
Confirmación de la nueva configuración del
