Copiar e colar no Xamarin.Mac

Este artigo aborda o trabalho com a área de trabalho para fornecer copiar e colar em um aplicativo Xamarin.Mac. Ele mostra como trabalhar com tipos de dados padrão que podem ser compartilhados entre vários aplicativos e como dar suporte a dados personalizados em um determinado aplicativo.

Visão geral

Ao trabalhar com C# e .NET em um aplicativo Xamarin.Mac, você tem acesso ao mesmo suporte de área de trabalho (copiar e colar) que um desenvolvedor que trabalha tem Objective-C .

Neste artigo, abordaremos as duas principais maneiras de usar a área de trabalho em um aplicativo Xamarin.Mac:

  1. Tipos de dados padrão - Como as operações de área de trabalho normalmente são realizadas entre dois aplicativos não relacionados, nenhum aplicativo sabe os tipos de dados que o outro suporta. Para maximizar o potencial de compartilhamento, a área de trabalho pode conter várias representações de um determinado item (usando um conjunto padrão de tipos de dados comuns), isso permite que o aplicativo de consumo escolha a versão mais adequada às suas necessidades.
  2. Dados personalizados - Para suportar a cópia e colagem de dados complexos no seu Xamarin.Mac, você pode definir um tipo de dados personalizado que será manipulado pela área de trabalho. Por exemplo, um aplicativo de desenho vetorial que permite ao usuário copiar e colar formas complexas compostas por vários tipos de dados e pontos.

Example of the running app

Neste artigo, abordaremos os conceitos básicos de como trabalhar com a área de trabalho em um aplicativo Xamarin.Mac para oferecer suporte a operações de copiar e colar. É altamente recomendável que você trabalhe primeiro no artigo Olá, Mac, especificamente nas seções Introdução ao Xcode e ao Construtor de Interface e Saídas e Ações, pois ele aborda os principais conceitos e técnicas que usaremos neste artigo.

Você pode querer dar uma olhada na seção Expondo classes C# / métodos paraObjective-Cdo documento Xamarin.Mac Internals também, ele explica os Register atributos e Export usados para conectar suas classes C# a Objective-C objetos e elementos da interface do usuário.

Introdução à área de trabalho

A área de trabalho apresenta um mecanismo padronizado para troca de dados dentro de um determinado aplicativo ou entre aplicativos. O uso típico para uma área de trabalho em um aplicativo Xamarin.Mac é para lidar com operações de copiar e colar, no entanto, várias outras operações também são suportadas (como arrastar e soltar e serviços de aplicativos).

Para tirá-lo do papel rapidamente, vamos começar com uma introdução simples e prática ao uso de pasteboards em um aplicativo Xamarin.Mac. Mais tarde, forneceremos uma explicação detalhada de como a área de trabalho funciona e os métodos usados.

Para este exemplo, criaremos um aplicativo simples baseado em documentos que gerencia uma janela contendo uma exibição de imagem. O usuário poderá copiar e colar imagens entre documentos no aplicativo e de ou para outros aplicativos ou várias janelas dentro do mesmo aplicativo.

Criando o projeto Xamarin

Primeiro, vamos criar um novo aplicativo Xamarin.Mac baseado em documento para o qual adicionaremos suporte a copiar e colar.

Faça o seguinte:

  1. Inicie o Visual Studio para Mac e clique no link Novo projeto... .

  2. Selecione Mac>App>Cocoa App e clique no botão Avançar:

    Creating a new Cocoa app project

  3. Digite MacCopyPaste o Nome do Projeto e mantenha todo o resto como padrão. Clique em Próximo:

    Setting the name of the project

  4. Clique no botão Criar :

    Confirming the new project settings

Adicionar um NSDocument

Em seguida, adicionaremos a classe personalizada NSDocument que atuará como o armazenamento em segundo plano para a interface do usuário do aplicativo. Ele conterá uma única Visualização de Imagem e saberá como copiar uma imagem da exibição para a área de trabalho padrão e como tirar uma imagem da área de trabalho padrão e exibi-la na Visualização de Imagem.

Clique com o botão direito do mouse no projeto Xamarin.Mac no Solution Pad e selecione Add>New File..:

Adding an NSDocument to the project

Digite ImageDocument para o Nome e clique no botão Novo. Edite a classe ImageDocument.cs e faça com que ela tenha a seguinte aparência:

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

Vamos dar uma olhada em alguns dos códigos em detalhes abaixo.

O código a seguir fornece uma propriedade para testar a existência de dados de imagem na área de trabalho padrão, se uma imagem estiver disponível, true será retornada senão 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);
    }
}

O código a seguir copia uma imagem da exibição de imagem anexada para a área de trabalho padrão:

[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 });
        }
    }

}

E o código a seguir cola uma imagem da área de trabalho padrão e a exibe na exibição de imagem anexada (se a área de trabalho contiver uma imagem 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]
    }
}

Com este documento, criaremos a interface do usuário para o aplicativo Xamarin.Mac.

Construindo a interface do usuário

Clique duas vezes no arquivo Main.storyboard para abri-lo no Xcode. Em seguida, adicione uma barra de ferramentas e uma imagem bem e configure-as da seguinte maneira:

Editing the toolbar

Adicione um Item da Barra de Ferramentas de Imagem de cópia e colagem ao lado esquerdo da barra de ferramentas. Vamos usá-los como atalhos para copiar e colar no menu Editar. Em seguida, adicione quatro Itens da Barra de Ferramentas de Imagem ao lado direito da barra de ferramentas. Vamos usá-los para preencher bem a imagem com algumas imagens padrão.

Para obter mais informações sobre como trabalhar com barras de ferramentas, consulte nossa documentação de barras de ferramentas.

Em seguida, vamos expor as seguintes saídas e ações para nossos itens da barra de ferramentas e a imagem bem:

Creating outlets and actions

Para obter mais informações sobre como trabalhar com pontos de venda e ações, consulte a seção Outlets e ações da nossa documentação do Hello, Mac .

Habilitando a interface do usuário

Com nossa interface de usuário criada no Xcode e nosso elemento de interface do usuário exposto por meio de saídas e ações, precisamos adicionar o código para habilitar a interface do usuário. Clique duas vezes no arquivo de ImageWindow.cs no Solution Pad e faça com que ele tenha a seguinte aparência:

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

Vamos dar uma olhada neste código em detalhes abaixo.

Primeiro, expomos uma instância da ImageDocument classe que criamos acima:

private ImageDocument _document;
...

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

ExportUsando , WillChangeValue e DidChangeValue, configuramos a Document propriedade para permitir a codificação de chave-valor e a vinculação de dados no Xcode.

Também expomos a imagem da imagem bem adicionada à nossa interface do usuário no Xcode com a seguinte propriedade:

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

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

Quando a janela principal é carregada e exibida, criamos uma instância de nossa ImageDocument classe e anexamos a imagem da interface do usuário bem a ela com o seguinte código:

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

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

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

Finalmente, em resposta ao usuário clicar nos itens da barra de ferramentas copiar e colar, chamamos a ImageDocument instância da classe para fazer o trabalho real:

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

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

Ativando os menus Arquivo e Editar

A última coisa que precisamos fazer é habilitar o item de menu Novo no menu Arquivo (para criar novas instâncias da nossa janela principal) e ativar os itens de menu Recortar, Copiar e Colar no menu Editar.

Para habilitar o item de menu Novo, edite o arquivo AppDelegate.cs e adicione o seguinte código:

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 obter mais informações, consulte a seção Trabalhando com várias janelas da nossa documentação do Windows .

Para habilitar os itens de menu Recortar, Copiar e Colar, edite o arquivo AppDelegate.cse adicione o seguinte código:

[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 item de menu, obtemos a janela de teclas atual, mais alta, e a lançamos para nossa ImageWindow classe:

var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

A partir daí, chamamos a ImageDocument instância de classe dessa janela para manipular as ações de copiar e colar. Por exemplo:

window.Document.CopyImage (sender);

Queremos apenas que os itens de menu Recortar, Copiar e Colar estejam acessíveis se houver dados de imagem na área de trabalho padrão ou no poço de imagem da janela ativa atual.

Vamos adicionar um arquivo EditMenuDelegate.cs ao projeto Xamarin.Mac e torná-lo parecido com o seguinte:

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

Novamente, obtemos a janela atual, mais alta, e usamos sua ImageDocument instância de classe para ver se os dados de imagem necessários existem. Em seguida, usamos o MenuWillHighlightItem método para habilitar ou desabilitar cada item com base nesse estado.

Edite o arquivo AppDelegate.cs e faça com que o DidFinishLaunching método tenha a seguinte aparência:

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

Primeiro, desativamos a ativação e desativação automática de itens de menu no menu Editar. Em seguida, anexamos uma instância da EditMenuDelegate classe que criamos acima.

Para obter mais informações, consulte nossa documentação de menus .

Testando o aplicativo

Com tudo pronto, estamos prontos para testar a aplicação. Crie e execute o aplicativo e a interface principal é exibida:

Running the application

Se você abrir o menu Editar, observe que Recortar, Copiar e Colar estão desabilitados porque não há nenhuma imagem na imagem ou na área de trabalho padrão:

Opening the Edit menu

Se você adicionar uma imagem à imagem e reabrir o menu Editar, os itens agora estarão habilitados:

Showing the Edit menu items are enabled

Se você copiar a imagem e selecionar Novo no menu Arquivo, poderá colar essa imagem na nova janela:

Pasting an image into a new window

Nas seções a seguir, daremos uma olhada detalhada no trabalho com a área de trabalho em um aplicativo Xamarin.Mac.

Sobre a pasteboard

No macOS (anteriormente conhecido como OS X), a área de trabalho (NSPasteboard) fornece suporte para vários processos de servidor, como Copiar e Colar, Arrastar e Soltar e Serviços de Aplicativos. Nas seções a seguir, examinaremos mais de perto vários conceitos-chave da área de trabalho.

O que é uma pasta?

A NSPasteboard classe fornece um mecanismo padronizado para troca de informações entre aplicativos ou dentro de um determinado aplicativo. A principal função de uma área de trabalho é lidar com operações de copiar e colar:

  1. Quando o usuário seleciona um item em um aplicativo e usa o item de menu Recortar ou Copiar , uma ou mais representações do item selecionado são colocadas na área de trabalho.
  2. Quando o usuário usa o item de menu Colar (dentro do mesmo aplicativo ou em um aplicativo diferente), a versão dos dados que ele pode manipular é copiada da área de trabalho e adicionada ao aplicativo.

Os usos menos óbvios da área de trabalho incluem localizar, arrastar, arrastar e soltar e operações de serviços de aplicativos:

  • Quando o usuário inicia uma operação de arrastar, os dados de arrastar são copiados para a área de trabalho. Se a operação de arrastar terminar com uma queda em outro aplicativo, esse aplicativo copiará os dados da área de trabalho.
  • Para serviços de tradução, os dados a serem traduzidos são copiados para a área de trabalho pelo aplicativo solicitante. O serviço do aplicativo, recupera os dados da área de trabalho, faz a conversão e, em seguida, cola os dados de volta na área de trabalho.

Em sua forma mais simples, as pasteboards são usadas para mover dados dentro de um determinado aplicativo ou entre aplicativos e, portanto, existem em uma área de memória global especial fora do processo do aplicativo. Embora os conceitos das pastilhas sejam facilmente compreendidos, há vários detalhes mais complexos que devem ser considerados. Estes serão abordados em detalhe a seguir.

Pasteboards nomeados

Uma área de trabalho pode ser pública ou privada e pode ser usada para uma variedade de finalidades dentro de um aplicativo ou entre vários aplicativos. O macOS fornece vários pasteboards padrão, cada um com um uso específico e bem definido:

  • NSGeneralPboard - A área de trabalho padrão para operações de Recortar, Copiar e Colar .
  • NSRulerPboard - Suporta operações de Recortar, Copiar e Colar em Réguas.
  • NSFontPboard - Suporta operações de Recortar, Copiar e Colar em NSFont objetos.
  • NSFindPboard - Suporta painéis de localização específicos do aplicativo que podem compartilhar texto de pesquisa.
  • NSDragPboard - Suporta operações de arrastar e soltar .

Para a maioria das situações, você usará uma das pastéis definidas pelo sistema. Mas pode haver situações que exijam que você crie seus próprios pastéis. Nessas situações, você pode usar o FromName (string name)NSPasteboard método da classe para criar uma área de trabalho personalizada com o nome fornecido.

Opcionalmente, você pode chamar o CreateWithUniqueNameNSPasteboard método da classe para criar uma área de trabalho com nome exclusivo.

Itens de área de trabalho

Cada parte de dados que um aplicativo grava em uma área de trabalho é considerada um item da área de trabalho e uma área de trabalho pode conter vários itens ao mesmo tempo. Dessa forma, um aplicativo pode gravar várias versões dos dados que estão sendo copiados para uma área de trabalho (por exemplo, texto sem formatação e texto formatado) e o aplicativo de recuperação pode ler apenas os dados que ele pode processar (como apenas texto sem formatação).

Representações de dados e identificadores de tipo uniformes

As operações de área de trabalho normalmente ocorrem entre dois (ou mais) aplicativos que não têm conhecimento um do outro ou dos tipos de dados que cada um pode manipular. Como indicado na seção acima, para maximizar o potencial de compartilhamento de informações, uma área de trabalho pode conter várias representações dos dados que estão sendo copiados e colados.

Cada representação é identificada por meio de um Uniform Type Identifier (UTI), que nada mais é do que uma simples cadeia de caracteres que identifica exclusivamente o tipo de data que está sendo apresentada (para obter mais informações, consulte a documentação Visão geral dos identificadores uniformes de tipo da Apple).

Se você estiver criando um tipo de dados personalizado (por exemplo, um objeto de desenho em um aplicativo de desenho vetorial), poderá criar sua própria UTI para identificá-lo exclusivamente em operações de copiar e colar.

Quando um aplicativo se prepara para colar dados copiados de uma área de trabalho, ele deve encontrar a representação que melhor se adapta às suas habilidades (se houver). Normalmente, esse será o tipo mais rico disponível (por exemplo, texto formatado para um aplicativo de processamento de texto), retornando aos formulários mais simples disponíveis conforme necessário (texto sem formatação para um editor de texto simples).

Dados prometidos

De um modo geral, você deve fornecer o maior número possível de representações dos dados que estão sendo copiados para maximizar o compartilhamento entre aplicativos. No entanto, devido a restrições de tempo ou memória, pode ser impraticável gravar cada tipo de dados na área de trabalho.

Nessa situação, você pode colocar a primeira representação de dados na área de trabalho e o aplicativo de recebimento pode solicitar uma representação diferente, que pode ser gerada imediatamente antes da operação de colagem.

Ao colocar o item inicial na área de trabalho, você especificará que uma ou mais das outras representações disponíveis são fornecidas por um objeto que está em conformidade com a NSPasteboardItemDataProvider interface. Esses objetos fornecerão as representações extras sob demanda, conforme solicitado pelo aplicativo de recebimento.

Contagem de alterações

Cada área de trabalho mantém uma Contagem de Alterações que aumenta cada vez que um novo proprietário é declarado. Um aplicativo pode determinar se o conteúdo da área de trabalho foi alterado desde a última vez que o examinou verificando o valor da Contagem de alterações.

Use os métodos e ClearContents da classe para modificar a ChangeCountNSPasteboard contagem de alterações de uma determinada área de trabalho.

Copiando dados para uma área de trabalho

Você executa uma operação de cópia acessando primeiro uma área de trabalho, limpando qualquer conteúdo existente e escrevendo quantas representações dos dados forem necessárias para a área de pasta.

Por exemplo:

// 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, você estará apenas escrevendo para a área de trabalho geral, como fizemos no exemplo acima. Qualquer objeto que você enviar para o WriteObjects método deve estar em conformidade com a INSPasteboardWriting interface. Várias classes internas (como NSString, NSImage, NSURL, NSColor, NSAttributedStringe NSPasteboardItem) estão automaticamente em conformidade com essa interface.

Se você estiver gravando uma classe de dados personalizada na área de trabalho, ela deverá estar em conformidade com a INSPasteboardWriting interface ou ser encapsulada em uma instância da NSPasteboardItem classe (consulte a seção Tipos de dados personalizados abaixo).

Lendo dados de uma área de trabalho

Como dito acima, para maximizar o potencial de compartilhamento de dados entre aplicativos, várias representações dos dados copiados podem ser gravadas na área de trabalho. Cabe ao aplicativo de recebimento selecionar a versão mais rica possível para seus recursos (se houver).

Operação de colagem simples

Você lê dados da área de trabalho usando o ReadObjectsForClasses método. Ele exigirá dois parâmetros:

  1. Uma matriz de tipos de NSObject classe baseados que você deseja ler da área de trabalho. Você deve ordenar isso com o tipo de dados mais desejado primeiro, com todos os tipos restantes em preferência decrescente.
  2. Um dicionário contendo restrições adicionais (como limitar a tipos de conteúdo de URL específicos) ou um dicionário vazio se nenhuma restrição adicional for necessária.

O método retorna uma matriz de itens que atendem aos critérios que passamos e, portanto, contém no máximo o mesmo número de tipos de dados solicitados. Também é possível que nenhum dos tipos solicitados esteja presente e uma matriz vazia seja retornada.

Por exemplo, o código a seguir verifica se existe um NSImage na área de trabalho geral e o exibe em uma imagem bem se existir:

[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];
            }
}

Solicitando vários tipos de dados

Com base no tipo de aplicativo Xamarin.Mac que está sendo criado, ele pode ser capaz de lidar com várias representações dos dados que estão sendo colados. Nessa situação, há dois cenários para recuperar dados da área de trabalho:

  1. Faça uma única chamada para o ReadObjectsForClasses método e forneça uma matriz de todas as representações que você deseja (na ordem preferida).
  2. Faça várias chamadas para o ReadObjectsForClasses método solicitando uma matriz diferente de tipos a cada vez.

Consulte a seção Operação de colagem simples acima para obter mais detalhes sobre como recuperar dados de uma área de trabalho.

Verificando tipos de dados existentes

Há momentos em que você pode querer verificar se uma área de trabalho contém uma determinada representação de dados sem realmente ler os dados da área de trabalho (como ativar o item de menu Colar somente quando houver dados válidos).

Chame o CanReadObjectForClasses método da área de trabalho para ver se ele contém um determinado tipo.

Por exemplo, o código a seguir determina se a área de trabalho geral contém uma NSImage instância:

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);
    }
}

Lendo urls da área de trabalho

Com base na função de um determinado aplicativo Xamarin.Mac, pode ser necessário ler URLs de uma área de trabalho, mas somente se eles atenderem a um determinado conjunto de critérios (como apontar para arquivos ou URLs de um tipo de dados específico). Nessa situação, você pode especificar critérios de pesquisa adicionais usando o segundo parâmetro dos CanReadObjectForClasses métodos ou ReadObjectsForClasses .

Tipos de dados personalizados

Há momentos em que você precisará salvar seus próprios tipos personalizados na área de trabalho a partir de um aplicativo Xamarin.Mac. Por exemplo, um aplicativo de desenho vetorial que permite ao usuário copiar e colar objetos de desenho.

Nessa situação, você precisará projetar sua classe personalizada de NSObject dados para que ela herde e esteja em conformidade com algumas interfaces (INSCoding, INSPasteboardWriting e INSPasteboardReading). Opcionalmente, você pode usar um NSPasteboardItem para encapsular os dados a serem copiados ou colados.

Ambas as opções serão abordadas em detalhes abaixo.

Usando uma classe personalizada

Nesta seção, expandiremos o aplicativo de exemplo simples que criamos no início deste documento e adicionaremos uma classe personalizada para rastrear informações sobre a imagem que estamos copiando e colando entre janelas.

Adicione uma nova classe ao projeto e chame-a de ImageInfo.cs. Edite o arquivo e faça com que ele tenha a seguinte aparência:

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

Nas seções a seguir, daremos uma olhada detalhada nessa classe.

Herança e interfaces

Antes que uma classe de dados personalizada possa ser gravada ou lida em uma área de trabalho, ela deve estar em conformidade com as INSPastebaordWriting interfaces e INSPasteboardReading . Além disso, ele deve herdar e NSObject também estar em conformidade com a INSCoding interface:

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

A classe também deve ser exposta ao Objective-C uso da Register diretiva e deve expor quaisquer propriedades ou métodos necessários usando Exporto . Por exemplo:

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

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

Estamos expondo os dois campos de dados que essa classe conterá - o nome da imagem e seu tipo (jpg, png, etc.).

Para obter mais informações, consulte a seção Expondo classes / métodos C# para Objective-C da documentação Xamarin.Mac Internals , ela explica os Register atributos usados Export para conectar suas classes C# a Objective-C objetos e elementos da interface do usuário.

Construtores

Dois construtores (devidamente expostos a Objective-C) serão necessários para nossa classe de dados personalizada para que ela possa ser lida de uma área de trabalho:

[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 ();
}

Primeiro, expomos o construtor vazio sob o método padrão Objective-C de init.

Em seguida, expomos um NSCoding construtor compatível que será usado para criar uma nova instância do objeto da área de trabalho ao colar sob o nome exportado de initWithCoder.

Esse construtor pega um NSCoder (como criado por um NSKeyedArchiver quando gravado na área de trabalho), extrai os dados emparelhados chave/valor e os salva nos campos de propriedade da classe de dados.

Escrevendo na área de trabalho

Ao nos conformarmos com a INSPasteboardWriting interface, precisamos expor dois métodos e, opcionalmente, um terceiro método, para que a classe possa ser gravada na área de trabalho.

Primeiro, precisamos dizer à área de trabalho em quais representações de tipo de dados a classe personalizada pode ser gravada:

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

Cada representação é identificada por meio de um Uniform Type Identifier (UTI), que nada mais é do que uma simples cadeia de caracteres que identifica exclusivamente o tipo de dados que está sendo apresentado (para obter mais informações, consulte a documentação Visão geral dos identificadores de tipo uniformes da Apple).

Para nosso formato personalizado, estamos criando nossa própria UTI: "com.xamarin.image-info" (observe que está em notação reversa assim como um identificador de aplicativo). Nossa classe também é capaz de escrever uma string padrão para a área de trabalho (public.text).

Em seguida, precisamos criar o objeto no formato solicitado que realmente é gravado na área de trabalho:

[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 o public.text tipo, estamos retornando um objeto simples e formatado NSString . Para o tipo personalizado com.xamarin.image-info , estamos usando uma NSKeyedArchiver interface e a NSCoder para codificar a classe de dados personalizada em um arquivo emparelhado chave/valor. Precisaremos implementar o seguinte método para realmente lidar com a codificação:

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

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

Os pares chave/valor individuais são gravados no codificador e serão decodificados usando o segundo construtor que adicionamos acima.

Opcionalmente, podemos incluir o seguinte método para definir quaisquer opções ao gravar dados na área de trabalho:

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

Atualmente, apenas a WritingPromised opção está disponível e deve ser usada quando um determinado tipo é apenas prometido e não realmente escrito na área de trabalho. Para obter mais informações, consulte a seção Dados Prometidos acima.

Com esses métodos em vigor, o código a seguir pode ser usado para gravar nossa classe personalizada na área de trabalho:

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

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

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

Leitura da área de trabalho

Em conformidade com a INSPasteboardReading interface, precisamos expor três métodos para que a classe de dados personalizada possa ser lida da área de trabalho.

Primeiro, precisamos dizer à área de trabalho quais representações de tipo de dados a classe personalizada pode ler da área de transferência:

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

Novamente, eles são definidos como UTIs simples e são os mesmos tipos que definimos na seção Escrevendo na área de trabalho acima.

Em seguida, precisamos dizer à área de trabalho como cada um dos tipos de ITU será lido usando o seguinte método:

[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 o com.xamarin.image-info tipo, estamos dizendo à área de trabalho para decodificar o par chave/valor que criamos com o NSKeyedArchiver ao escrever a classe na área de trabalho chamando o initWithCoder: construtor que adicionamos à classe.

Finalmente, precisamos adicionar o seguinte método para ler as outras representações de dados UTI da área de trabalho:

[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;
}

Com todos esses métodos no lugar, a classe de dados personalizada pode ser lida da área de trabalho usando o seguinte código:

// 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];
}

Usando um NSPasteboardItem

Pode haver momentos em que você precise gravar itens personalizados na área de trabalho que não garantam a criação de uma classe personalizada ou que você deseja fornecer dados em um formato comum, apenas conforme necessário. Para essas situações, você pode usar um NSPasteboardItemarquivo .

A NSPasteboardItem fornece controle refinado sobre os dados que são gravados na área de trabalho e é projetado para acesso temporário - ele deve ser descartado depois de ter sido gravado na área de trabalho.

Gravação de dados

Para gravar seus dados personalizados em um NSPasteboardItemNSPasteboardItemDataProviderarquivo . Adicione uma nova classe ao projeto e chame-a de ImageInfoDataProvider.cs. Edite o arquivo e faça com que ele tenha a seguinte aparência:

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 fizemos com a classe de dados personalizada, precisamos usar as Register diretivas e Export para expô-la ao Objective-C. A classe deve herdar de NSPasteboardItemDataProvider e deve implementar os FinishedWithDataProvider métodos e ProvideDataForType .

Use o ProvideDataForType método para fornecer os dados que serão encapsulados NSPasteboardItem da seguinte maneira:

[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;
    }

}

Nesse caso, estamos armazenando duas informações sobre nossa imagem (Name e ImageType) e gravando-as em uma cadeia de caracteres simples (public.text).

Digite gravar os dados na área de trabalho, use o seguinte código:

// 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 });
}

Lendo dados

Para ler os dados de volta da área de trabalho, use o seguinte código:

// 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
    ...
}

Resumo

Este artigo deu uma olhada detalhada no trabalho com a área de trabalho em um aplicativo Xamarin.Mac para oferecer suporte a operações de copiar e colar. Primeiro, ele introduziu um exemplo simples para familiarizá-lo com as operações padrão de pasteboards. Em seguida, deu uma olhada detalhada na área de trabalho e como ler e gravar dados a partir dela. Por fim, analisou o uso de um tipo de dados personalizado para dar suporte à cópia e colagem de tipos de dados complexos em um aplicativo.