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:
- 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.
- 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.
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 para Objective-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:
Inicie o Visual Studio para Mac e clique no link Novo projeto... .
Selecione Mac>App>Cocoa App e clique no botão Avançar:
Digite
MacCopyPaste
o Nome do Projeto e mantenha todo o resto como padrão. Clique em Próximo:Clique no botão Criar :
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..:
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:
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:
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");
}
}
Export
Usando , 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.cs e 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:
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:
Se você adicionar uma imagem à imagem e reabrir o menu Editar, os itens agora estarão habilitados:
Se você copiar a imagem e selecionar Novo no menu Arquivo, poderá colar essa imagem na nova janela:
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:
- 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.
- 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 emNSFont
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 CreateWithUniqueName
NSPasteboard
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 ChangeCount
NSPasteboard
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
, NSAttributedString
e 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:
- 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. - 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:
- 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). - 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 Export
o . 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 NSPasteboardItem
arquivo .
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 NSPasteboardItem
NSPasteboardItemDataProvider
arquivo . 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.