Copiare e incollare in Xamarin.Mac
Questo articolo illustra l'uso della lavagna per fornire copia e incolla in un'applicazione Xamarin.Mac. Illustra come usare i tipi di dati standard che possono essere condivisi tra più app e come supportare i dati personalizzati all'interno di una determinata app.
Panoramica
Quando si lavora con C# e .NET in un'applicazione Xamarin.Mac, è possibile accedere allo stesso incollatore (copia e incolla) supportato da uno sviluppatore che lavora.Objective-C
In questo articolo verranno descritti i due modi principali per usare la lavagna in un'app Xamarin.Mac:
- Tipi di dati standard: poiché le operazioni incollaboard vengono in genere eseguite tra due app non correlate, nessuna delle due app conosce i tipi di dati supportati dall'altro. Per massimizzare il potenziale di condivisione, la lavagna può contenere più rappresentazioni di un determinato elemento (usando un set standard di tipi di dati comuni), in modo che l'app che utilizza la selezione della versione più adatta alle proprie esigenze.
- Dati personalizzati: per supportare la copia e incolla di dati complessi all'interno di Xamarin.Mac, è possibile definire un tipo di dati personalizzato che verrà gestito dalla lavagna. Ad esempio, un'app di disegno vettoriale che consente all'utente di copiare e incollare forme complesse composte da più tipi di dati e punti.
In questo articolo verranno illustrate le nozioni di base sull'uso della lavagna in un'applicazione Xamarin.Mac per supportare le operazioni di copia e incolla. È consigliabile usare prima di tutto l'articolo Hello, Mac , in particolare le sezioni Introduzione a Xcode e Interface Builder e Outlet e Actions , in quanto illustra i concetti e le tecniche chiave che verranno usati in questo articolo.
È possibile esaminare anche la sezione Esposizione di classi/metodi C# al Objective-C documento Internals di Xamarin.Mac, che illustra gli Register
attributi e Export
usati per collegare le classi C# agli oggetti e agli Objective-C elementi dell'interfaccia utente.
Introduzione alla lavagna
La lavagna presenta un meccanismo standardizzato per lo scambio di dati all'interno di una determinata applicazione o tra applicazioni. L'uso tipico per una lavagna in un'applicazione Xamarin.Mac consiste nell'gestire le operazioni di copia e incolla, ma sono supportate anche diverse altre operazioni, ad esempio trascinamento e rilascio e servizi applicazioni.
Per iniziare rapidamente, inizieremo con una semplice introduzione pratica all'uso delle lavagne in un'app Xamarin.Mac. Successivamente, verrà fornita una spiegazione approfondita del funzionamento della lavagna e dei metodi usati.
Per questo esempio verrà creata una semplice applicazione basata su documenti che gestisce una finestra contenente una visualizzazione immagine. L'utente potrà copiare e incollare immagini tra documenti nell'app e da o verso altre app o più finestre all'interno della stessa app.
Creazione del progetto Xamarin
Prima di tutto verrà creata una nuova app Xamarin.Mac basata su documento per cui verrà aggiunto il supporto copia e incolla.
Effettua le operazioni seguenti:
Avviare Visual Studio per Mac e fare clic sul collegamento Nuovo progetto.
Selezionare Mac>App Cocoa App> e quindi fare clic sul pulsante Avanti:
Immettere
MacCopyPaste
per Nome progetto e mantenere tutto il resto come predefinito. Fare clic su Avanti:Fare clic sul pulsante Crea :
Aggiungere un NSDocument
Successivamente si aggiungerà una classe personalizzata NSDocument
che fungerà da risorsa di archiviazione in background per l'interfaccia utente dell'applicazione. Conterrà una singola visualizzazione immagini e saprà come copiare un'immagine dalla visualizzazione nella lavagna predefinita e come acquisire un'immagine dalla lavagna predefinita e visualizzarla nella visualizzazione immagini.
Fare clic con il pulsante destro del mouse sul progetto Xamarin.Mac nel riquadro della soluzione e scegliere Aggiungi>nuovo file..:
Immettere ImageDocument
in Nome e fare clic sul pulsante Nuovo. Modificare la classe ImageDocument.cs e impostarla come segue:
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
}
}
Di seguito è riportato un esempio di codice.
Il codice seguente fornisce una proprietà per verificare l'esistenza di dati immagine nella lavagna predefinita, se è disponibile un'immagine, true
viene restituita un'altra 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);
}
}
Il codice seguente copia un'immagine dalla visualizzazione immagine associata nella lavagna predefinita:
[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 });
}
}
}
Il codice seguente incolla un'immagine dalla lavagna predefinita e la visualizza nella visualizzazione immagine associata (se la lavagna contiene un'immagine valida):
[Export("PasteImage:")]
public void PasteImage(NSObject sender) {
// Initialize the pasteboard
NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
Class [] classArray = { new Class ("NSImage") };
bool ok = pasteboard.CanReadObjectForClasses (classArray, null);
if (ok) {
// Read the image off of the pasteboard
NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray, null);
NSImage image = (NSImage)objectsToPaste[0];
// Display the new image
ImageView.Image = image;
}
Class [] classArray2 = { new Class ("ImageInfo") };
ok = pasteboard.CanReadObjectForClasses (classArray2, null);
if (ok) {
// Read the image off of the pasteboard
NSObject [] objectsToPaste = pasteboard.ReadObjectsForClasses (classArray2, null);
ImageInfo info = (ImageInfo)objectsToPaste[0]
}
}
Con questo documento, verrà creata l'interfaccia utente per l'app Xamarin.Mac.
Compilazione dell'interfaccia utente
Fare doppio clic sul file Main.storyboard per aprirlo in Xcode. Aggiungere quindi una barra degli strumenti e un'immagine e configurarle nel modo seguente:
Aggiungere una copia e incollare l'elemento della barra degli strumenti immagine sul lato sinistro della barra degli strumenti. Verranno usati come tasti di scelta rapida per copiare e incollare dal menu Modifica. Aggiungere quindi quattro elementi della barra degli strumenti immagine sul lato destro della barra degli strumenti. Queste immagini verranno usate per popolare l'immagine con alcune immagini predefinite.
Per altre informazioni sull'uso delle barre degli strumenti, vedere la documentazione sulle barre degli strumenti.
Verranno ora esposte le azioni e i punti vendita seguenti per gli elementi della barra degli strumenti e l'immagine:
Per altre informazioni sull'uso di outlet e azioni, vedere la sezione Outlet e azioni della documentazione di Hello, Mac .
Abilitazione dell'interfaccia utente
Con l'interfaccia utente creata in Xcode e l'elemento dell'interfaccia utente esposto tramite outlet e azioni, è necessario aggiungere il codice per abilitare l'interfaccia utente. Fare doppio clic sul file ImageWindow.cs nel riquadro della soluzione e impostarlo come segue:
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
}
}
Di seguito viene illustrato in dettaglio questo codice.
Prima di tutto, viene esposta un'istanza della ImageDocument
classe creata in precedenza:
private ImageDocument _document;
...
[Export ("Document")]
public ImageDocument Document {
get { return _document; }
set {
WillChangeValue ("Document");
_document = value;
DidChangeValue ("Document");
}
}
Usando Export
, WillChangeValue
e DidChangeValue
, è stata configurata la Document
proprietà per consentire la codifica chiave-valore e il data binding in Xcode.
Si espone anche l'immagine dall'immagine aggiunta all'interfaccia utente in Xcode con la proprietà seguente:
public ViewController ImageViewController {
get { return ContentViewController as ViewController; }
}
public NSImage Image {
get {
return ImageViewController.Image;
}
set {
ImageViewController.Image = value;
}
}
Quando viene caricata e visualizzata la finestra principale, viene creata un'istanza della ImageDocument
classe e si associa l'immagine dell'interfaccia utente al codice seguente:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Create a new document instance
Document = new ImageDocument ();
// Attach to image view
Document.ImageView = ImageViewController.ContentView;
}
Infine, in risposta all'utente che fa clic sugli elementi della barra degli strumenti copia e incolla, chiamiamo l'istanza della ImageDocument
classe per eseguire il lavoro effettivo:
partial void CopyImage (NSObject sender) {
Document.CopyImage(sender);
}
partial void PasteImage (Foundation.NSObject sender) {
Document.PasteImage(sender);
}
Abilitazione dei menu File e Modifica
L'ultima cosa da fare è abilitare la voce di menu Nuovo dal menu File (per creare nuove istanze della finestra principale) e per abilitare le voci di menu Taglia, Copia e Incolla dal menu Modifica .
Per abilitare la voce di menu Nuovo , modificare il file AppDelegate.cs e aggiungere il codice seguente:
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);
}
Per altre informazioni, vedere la sezione Uso di più finestre della documentazione di Windows .
Per abilitare le voci di menu Taglia, Copia e Incolla , modificare il file AppDelegate.cs e aggiungere il codice seguente:
[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);
}
Per ogni voce di menu, si ottiene la finestra di tasti corrente, in alto e la si esegue il cast alla ImageWindow
classe:
var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;
Da qui viene chiamata l'istanza ImageDocument
della classe di tale finestra per gestire le azioni di copia e incolla. Ad esempio:
window.Document.CopyImage (sender);
Si vuole che le voci di menu Taglia, Copia e Incolla siano accessibili solo se sono presenti dati immagine nella lavagna predefinita o nell'area immagine della finestra attiva corrente.
Aggiungere un file EditMenuDelegate.cs al progetto Xamarin.Mac e renderlo simile al seguente:
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
}
}
Anche in questo caso, si ottiene la finestra corrente più in alto e si usa l'istanza ImageDocument
della classe per verificare se i dati di immagine necessari esistono. Viene quindi usato il MenuWillHighlightItem
metodo per abilitare o disabilitare ogni elemento in base a questo stato.
Modificare il file AppDelegate.cs e fare in modo che il DidFinishLaunching
metodo sia simile al seguente:
public override void DidFinishLaunching (NSNotification notification)
{
// Disable automatic item enabling on the Edit menu
EditMenu.AutoEnablesItems = false;
EditMenu.Delegate = new EditMenuDelegate ();
}
Prima di tutto, disabilitiamo l'abilitazione automatica e la disabilitazione delle voci di menu nel menu Modifica. Successivamente, si allega un'istanza della EditMenuDelegate
classe creata in precedenza.
Per altre informazioni, vedere la documentazione relativa ai menu .
Test dell'app
Con tutti gli elementi disponibili, è possibile testare l'applicazione. Compilare ed eseguire l'app e viene visualizzata l'interfaccia principale:
Se si apre il menu Modifica, si noti che Taglia, Copia e Incolla sono disabilitati perché nell'area immagine non è presente alcuna immagine o nella lavagna predefinita:
Se si aggiunge un'immagine all'immagine e si riapre il menu Modifica, le voci verranno ora abilitate:
Se si copia l'immagine e si seleziona Nuovo dal menu file, è possibile incollare l'immagine nella nuova finestra:
Nelle sezioni seguenti si esaminerà in dettaglio l'uso della lavagna in un'applicazione Xamarin.Mac.
Informazioni sulla lavagna
In macOS (in precedenza noto come OS X) la lavagna (NSPasteboard
) fornisce il supporto per diversi processi server, ad esempio Copia e Incolla, Drag & Drop e Servizi applicazioni. Nelle sezioni seguenti verranno esaminati in modo più approfondito alcuni concetti chiave della lavagna.
Che cos'è una lavagna?
La NSPasteboard
classe fornisce un meccanismo standardizzato per lo scambio di informazioni tra applicazioni o all'interno di una determinata app. La funzione principale di una lavagna consiste nella gestione delle operazioni di copia e incolla:
- Quando l'utente seleziona un elemento in un'app e usa la voce di menu Taglia o Copia , una o più rappresentazioni dell'elemento selezionato vengono posizionate sulla lavagna.
- Quando l'utente usa la voce di menu Incolla (all'interno della stessa app o diversa), la versione dei dati che può gestire viene copiata dalla lavagna e aggiunta all'app.
Gli usi meno ovvi della lavagna includono le operazioni di ricerca, trascinamento, trascinamento della selezione e servizi dell'applicazione:
- Quando l'utente avvia un'operazione di trascinamento, i dati di trascinamento sono copiati nella lavagna. Se l'operazione di trascinamento termina con un rilascio in un'altra app, tale app copia i dati dalla lavagna.
- Per i servizi di traduzione, i dati da tradurre vengono copiati nella lavagna dall'app richiedente. Il servizio dell'applicazione recupera i dati dalla lavagna, esegue la traduzione, quindi incolla i dati nella lavagna.
Nel loro formato più semplice, le lavagne vengono usate per spostare i dati all'interno di una determinata app o tra app e esistono in un'area di memoria globale speciale all'esterno del processo dell'app. Anche se i concetti delle lavagne sono facilmente comprensibili, è necessario considerare diversi dettagli più complessi. Questi verranno trattati in dettaglio di seguito.
Pasteboard denominate
Una lavagna può essere pubblica o privata e può essere usata per diversi scopi all'interno di un'applicazione o tra più app. macOS offre diverse lavagne standard, ognuna con un utilizzo specifico e ben definito:
NSGeneralPboard
- Tabellone predefinito per le operazioni Taglia, Copia e Incolla .NSRulerPboard
- Supporta le operazioni Taglia, Copia e Incolla sui righelli.NSFontPboard
- Supporta le operazioni Taglia, Copia e Incolla sugliNSFont
oggetti .NSFindPboard
- Supporta pannelli di ricerca specifici dell'applicazione che possono condividere il testo di ricerca.NSDragPboard
- Supporta le operazioni di trascinamento e rilascio .
Per la maggior parte delle situazioni, si userà una delle lavagne definite dal sistema. Potrebbero tuttavia verificarsi situazioni che richiedono di creare lavagne personalizzate. In queste situazioni, è possibile usare il FromName (string name)
metodo della NSPasteboard
classe per creare una lavagna personalizzata con il nome specificato.
Facoltativamente, è possibile chiamare il CreateWithUniqueName
metodo della NSPasteboard
classe per creare una lavagna denominata in modo univoco.
Elementi della lavagna
Ogni parte di dati che un'applicazione scrive in una lavagna è considerata un elemento incollato e una lavagna può contenere più elementi contemporaneamente. In questo modo, un'app può scrivere più versioni dei dati copiati in una lavagna (ad esempio testo normale e testo formattato) e l'app di recupero può leggere solo i dati che possono elaborare (ad esempio solo testo normale).
Rappresentazioni dei dati e identificatori di tipo uniformi
Le operazioni incollaboard vengono in genere eseguite tra due o più applicazioni che non hanno alcuna conoscenza tra loro o i tipi di dati che ognuno può gestire. Come indicato nella sezione precedente, per massimizzare il potenziale di condivisione delle informazioni, una lavagna può contenere più rappresentazioni dei dati copiati e incollati.
Ogni rappresentazione viene identificata tramite un UTI (Uniform Type Identifier), che non è altro che una stringa semplice che identifica in modo univoco il tipo di data da presentare (per altre informazioni, vedere la documentazione di Panoramica sugli identificatori uniformi dei tipi di Apple).
Se si crea un tipo di dati personalizzato (ad esempio, un oggetto disegno in un'app di disegno vettoriale), è possibile creare un'UTI personalizzata per identificarla in modo univoco nelle operazioni di copia e incolla.
Quando un'app si prepara a incollare i dati copiati da una lavagna, deve trovare la rappresentazione più adatta alle proprie capacità (se presente). In genere, questo sarà il tipo più ricco disponibile (ad esempio testo formattato per un'app per l'elaborazione di parole), il fallback ai moduli più semplici disponibili in base alle esigenze (testo normale per un semplice editor di testo).
Dati promessi
In generale, è consigliabile fornire il maggior numero possibile di rappresentazioni dei dati copiati per ottimizzare la condivisione tra le app. Tuttavia, a causa di vincoli di tempo o memoria, potrebbe essere poco pratico scrivere effettivamente ogni tipo di dati nella lavagna.
In questo caso, è possibile inserire la prima rappresentazione dei dati sulla lavagna e l'app ricevente può richiedere una rappresentazione diversa, che può essere generata immediatamente prima dell'operazione incolla.
Quando si inserisce l'elemento iniziale nella lavagna, si specifica che una o più delle altre rappresentazioni disponibili vengono fornite da un oggetto conforme all'interfaccia NSPasteboardItemDataProvider
. Questi oggetti forniranno le rappresentazioni aggiuntive su richiesta, come richiesto dall'app ricevente.
Conteggio modifiche
Ogni tabellone mantiene un conteggio delle modifiche che incrementa ogni volta che viene dichiarato un nuovo proprietario. Un'app può determinare se il contenuto della lavagna è cambiato dall'ultima volta che lo ha esaminato controllando il valore di Conteggio modifiche.
Utilizzare i ChangeCount
metodi e ClearContents
della NSPasteboard
classe per modificare il conteggio delle modifiche di un determinato tabellone.
Copia di dati in una lavagna
Per eseguire un'operazione di copia, accedere prima a una lavagna, cancellare qualsiasi contenuto esistente e scrivere tutte le rappresentazioni dei dati necessarie per la lavagna.
Ad esempio:
// 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});
In genere, si scriverà solo nella lavagna generale, come è stato fatto nell'esempio precedente. Qualsiasi oggetto inviato al WriteObjects
metodo deve essere conforme all'interfaccia INSPasteboardWriting
. Diverse classi predefinite( ad esempio NSString
, NSColor
NSAttributedString
NSImage
NSURL
e NSPasteboardItem
) sono conformi automaticamente a questa interfaccia.
Se si scrive una classe di dati personalizzata nella lavagna, deve essere conforme all'interfaccia INSPasteboardWriting
o essere sottoposta a wrapping in un'istanza della NSPasteboardItem
classe . Vedere la sezione Tipi di dati personalizzati di seguito.
Lettura di dati da una lavagna
Come indicato in precedenza, per massimizzare il potenziale di condivisione dei dati tra app, è possibile scrivere più rappresentazioni dei dati copiati nella lavagna. Spetta all'app ricevente selezionare la versione più ricca possibile per le sue funzionalità (se presente).
Operazione incolla semplice
I dati sono letti dalla lavagna usando il ReadObjectsForClasses
metodo . Richiederà due parametri:
- Matrice di tipi di
NSObject
classe basati che si desidera leggere dalla lavagna. È consigliabile ordinare prima di tutto questo con il tipo di dati più desiderato, con eventuali tipi rimanenti in preferenza decrescente. - Dizionario contenente vincoli aggiuntivi ,ad esempio la limitazione a tipi di contenuto URL specifici, o un dizionario vuoto se non sono necessari ulteriori vincoli.
Il metodo restituisce una matrice di elementi che soddisfano i criteri passati e pertanto contiene al massimo lo stesso numero di tipi di dati richiesti. È anche possibile che nessuno dei tipi richiesti sia presente e che venga restituita una matrice vuota.
Ad esempio, il codice seguente verifica se esiste un NSImage
oggetto nella lavagna generale e lo visualizza in un'immagine se lo fa:
[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];
}
}
Richiesta di più tipi di dati
In base al tipo di applicazione Xamarin.Mac in fase di creazione, può essere in grado di gestire più rappresentazioni dei dati incollati. In questo caso, esistono due scenari per il recupero dei dati dalla lavagna:
- Effettuare una singola chiamata al
ReadObjectsForClasses
metodo e fornire una matrice di tutte le rappresentazioni desiderate (nell'ordine preferito). - Effettuare più chiamate al
ReadObjectsForClasses
metodo chiedendo una matrice di tipi diversa ogni volta.
Per altri dettagli sul recupero dei dati da una lavagna, vedere la sezione Operazione Incolla semplice precedente.
Controllo dei tipi di dati esistenti
In alcuni casi è possibile verificare se una lavagna contiene una determinata rappresentazione dei dati senza effettivamente leggere i dati dalla lavagna, ad esempio abilitando la voce di menu Incolla solo quando esistono dati validi.
Chiamare il CanReadObjectForClasses
metodo della lavagna per verificare se contiene un determinato tipo.
Ad esempio, il codice seguente determina se la lavagna generale contiene un'istanza NSImage
:
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);
}
}
Lettura degli URL dalla lavagna
In base alla funzione di una determinata app Xamarin.Mac, potrebbe essere necessario leggere gli URL da una lavagna, ma solo se soddisfano un determinato set di criteri (ad esempio puntando a file o URL di un tipo di dati specifico). In questo caso, è possibile specificare criteri di ricerca aggiuntivi usando il secondo parametro dei CanReadObjectForClasses
metodi o ReadObjectsForClasses
.
Tipi di dati personalizzati
In alcuni casi è necessario salvare i propri tipi personalizzati nella lavagna da un'app Xamarin.Mac. Ad esempio, un'app di disegno vettoriale che consente all'utente di copiare e incollare oggetti disegno.
In questo caso, è necessario progettare la classe personalizzata dei dati in modo che erediti da NSObject
e sia conforme a alcune interfacce (INSCoding
INSPasteboardWriting
e INSPasteboardReading
). Facoltativamente, è possibile usare un oggetto NSPasteboardItem
per incapsulare i dati da copiare o incollare.
Entrambe queste opzioni verranno illustrate in dettaglio di seguito.
Uso di una classe personalizzata
In questa sezione si espanderà l'app di esempio semplice creata all'inizio di questo documento e si aggiungerà una classe personalizzata per tenere traccia delle informazioni sull'immagine copiata e incollata tra le finestre.
Aggiungere una nuova classe al progetto e chiamarla ImageInfo.cs. Modificare il file e renderlo simile al seguente:
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
}
}
Nelle sezioni seguenti verrà esaminato in dettaglio questa classe.
Ereditarietà e interfacce
Prima che una classe di dati personalizzata possa essere scritta o letta da una lavagna, deve essere conforme alle INSPastebaordWriting
interfacce e INSPasteboardReading
. Inoltre, deve ereditare da NSObject
e anche essere conforme all'interfaccia INSCoding
:
[Register("ImageInfo")]
public class ImageInfo : NSObject, INSCoding, INSPasteboardWriting, INSPasteboardReading
...
La classe deve anche essere esposta all'uso Objective-C della Register
direttiva e deve esporre eventuali proprietà o metodi necessari tramite Export
. Ad esempio:
[Export("name")]
public string Name { get; set; }
[Export("imageType")]
public string ImageType { get; set; }
Vengono esposti i due campi di dati che questa classe conterrà: il nome dell'immagine e il relativo tipo (jpg, png e così via).
Per altre informazioni, vedere la sezione Esposizione di classi/metodi C# alla Objective-C documentazione di Xamarin.Mac Internals , illustra gli Register
attributi e Export
usati per collegare le classi C# agli oggetti e agli Objective-C elementi dell'interfaccia utente.
Costruttori
Per la classe di dati personalizzata saranno necessari due costruttori (esposti correttamente a Objective-C) in modo che possano essere letti da una lavagna:
[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 ();
}
Prima di tutto, viene esposto il costruttore vuoto nel metodo predefinito Objective-C di init
.
Verrà quindi esposto un NSCoding
costruttore conforme che verrà usato per creare una nuova istanza dell'oggetto dalla lavagna quando si incolla sotto il nome esportato di initWithCoder
.
Questo costruttore accetta ( NSCoder
come creato da un NSKeyedArchiver
oggetto quando viene scritto nella lavagna), estrae i dati associati chiave/valore e lo salva nei campi delle proprietà della classe di dati.
Scrittura nella lavagna
Con la conformità all'interfaccia INSPasteboardWriting
, è necessario esporre due metodi e, facoltativamente, un terzo metodo, in modo che la classe possa essere scritta nella lavagna.
Prima di tutto, è necessario indicare alla lavagna quali rappresentazioni del tipo di dati possono essere scritte nella classe personalizzata:
[Export ("writableTypesForPasteboard:")]
public virtual string[] GetWritableTypesForPasteboard (NSPasteboard pasteboard) {
string[] writableTypes = {"com.xamarin.image-info", "public.text"};
return writableTypes;
}
Ogni rappresentazione viene identificata tramite un UTI (Uniform Type Identifier), che non è altro che una stringa semplice che identifica in modo univoco il tipo di dati presentato (per altre informazioni, vedere la documentazione di Panoramica degli identificatori uniformi dei tipi di Apple).
Per il formato personalizzato, stiamo creando la nostra UTI: "com.xamarin.image-info" (si noti che è in notazione inversa proprio come un identificatore dell'app). La classe è anche in grado di scrivere una stringa standard nella lavagna (public.text
).
Successivamente, è necessario creare l'oggetto nel formato richiesto che viene effettivamente scritto nella lavagna:
[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;
}
Per il public.text
tipo viene restituito un oggetto semplice formattato NSString
. Per il tipo personalizzato com.xamarin.image-info
, viene usata un'interfaccia NSKeyedArchiver
NSCoder
e per codificare la classe di dati personalizzata in un archivio a chiave/valore associato. Per gestire effettivamente la codifica, è necessario implementare il metodo seguente:
[Export ("encodeWithCoder:")]
public void EncodeTo (NSCoder encoder) {
// Encode data
encoder.Encode(new NSString(Name),"name");
encoder.Encode(new NSString(ImageType),"imageType");
}
Le singole coppie chiave/valore vengono scritte nel codificatore e verranno decodificate usando il secondo costruttore aggiunto in precedenza.
Facoltativamente, è possibile includere il metodo seguente per definire qualsiasi opzione durante la scrittura di dati nella lavagna:
[Export ("writingOptionsForType:pasteboard:"), CompilerGenerated]
public virtual NSPasteboardWritingOptions GetWritingOptionsForType (string type, NSPasteboard pasteboard) {
return NSPasteboardWritingOptions.WritingPromised;
}
Attualmente è disponibile solo l'opzione WritingPromised
e deve essere usata quando un determinato tipo viene promesso e non effettivamente scritto nella lavagna. Per altre informazioni, vedere la sezione Dati promessi sopra.
Con questi metodi sul posto, è possibile usare il codice seguente per scrivere la classe personalizzata nella lavagna:
// Get the standard pasteboard
var pasteboard = NSPasteboard.GeneralPasteboard;
// Empty the current contents
pasteboard.ClearContents();
// Add info to the pasteboard
pasteboard.WriteObjects (new ImageInfo[] { Info });
Lettura dalla lavagna
Con la conformità all'interfaccia INSPasteboardReading
, è necessario esporre tre metodi in modo che la classe di dati personalizzata possa essere letta dalla lavagna.
Prima di tutto, è necessario indicare alla lavagna quali rappresentazioni del tipo di dati possono essere lette dagli Appunti:
[Export ("readableTypesForPasteboard:")]
public static string[] GetReadableTypesForPasteboard (NSPasteboard pasteboard){
string[] readableTypes = {"com.xamarin.image-info", "public.text"};
return readableTypes;
}
Anche in questo caso, questi sono definiti come semplici UTI e sono gli stessi tipi definiti nella sezione Scrittura nella lavagna precedente.
Successivamente, è necessario indicare al tabellone come verranno letti ognuno dei tipi UTI usando il metodo seguente:
[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;
}
Per il com.xamarin.image-info
tipo, si indica alla lavagna di decodificare la coppia chiave/valore creata con durante NSKeyedArchiver
la scrittura della classe nella lavagna chiamando il initWithCoder:
costruttore aggiunto alla classe .
Infine, è necessario aggiungere il metodo seguente per leggere le altre rappresentazioni dei dati UTI dalla lavagna:
[Export ("initWithPasteboardPropertyList:ofType:")]
public NSObject InitWithPasteboardPropertyList (NSObject propertyList, string type) {
// Take action based on the requested type
switch (type) {
case "public.text":
return new ImageInfo();
}
// Failure, return null
return null;
}
Con tutti questi metodi sul posto, la classe di dati personalizzata può essere letta dalla lavagna usando il codice seguente:
// Initialize the pasteboard
NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
var classArrayPtrs = new [] { Class.GetHandle (typeof(ImageInfo)) };
NSArray classArray = NSArray.FromIntPtrs (classArrayPtrs);
// NOTE: Sending messages directly to the base Objective-C API because of this defect:
// https://bugzilla.xamarin.com/show_bug.cgi?id=31760
// Check to see if image info is on the pasteboard
ok = bool_objc_msgSend_IntPtr_IntPtr (pasteboard.Handle, Selector.GetHandle ("canReadObjectForClasses:options:"), classArray.Handle, IntPtr.Zero);
if (ok) {
// Read the image off of the pasteboard
NSObject [] objectsToPaste = NSArray.ArrayFromHandle<Foundation.NSObject>(IntPtr_objc_msgSend_IntPtr_IntPtr (pasteboard.Handle, Selector.GetHandle ("readObjectsForClasses:options:"), classArray.Handle, IntPtr.Zero));
ImageInfo info = (ImageInfo)objectsToPaste[0];
}
Uso di un oggetto NSPasteboardItem
In alcuni casi potrebbe essere necessario scrivere elementi personalizzati nella lavagna che non giustificano la creazione di una classe personalizzata o si desidera fornire dati in un formato comune, solo in base alle esigenze. Per queste situazioni, è possibile usare un oggetto NSPasteboardItem
.
Un NSPasteboardItem
oggetto fornisce un controllo con granularità fine sui dati scritti nella lavagna ed è progettato per l'accesso temporaneo, che deve essere eliminato dopo che è stato scritto nella lavagna.
Scrittura dei dati
Per scrivere i dati personalizzati in un NSPasteboardItem
oggetto è necessario fornire un oggetto personalizzato NSPasteboardItemDataProvider
. Aggiungere una nuova classe al progetto e chiamarla ImageInfoDataProvider.cs. Modificare il file e renderlo simile al seguente:
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
}
}
Come è stato fatto con la classe di dati personalizzata, è necessario usare le Register
direttive e Export
per esporla a Objective-C. La classe deve ereditare da NSPasteboardItemDataProvider
e deve implementare i FinishedWithDataProvider
metodi e ProvideDataForType
.
Usare il ProvideDataForType
metodo per fornire i dati di cui verrà eseguito il NSPasteboardItem
wrapping nel come indicato di seguito:
[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;
}
}
In questo caso, vengono archiviate due informazioni sull'immagine (Name e ImageType) e le si scrive in una stringa semplice (public.text
).
Digitare scrivere i dati nella lavagna, usare il codice seguente:
// 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 });
}
Lettura dei dati
Per leggere nuovamente i dati dalla lavagna, usare il codice seguente:
// 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
...
}
Riepilogo
Questo articolo ha esaminato in modo dettagliato l'uso della lavagna in un'applicazione Xamarin.Mac per supportare le operazioni di copia e incolla. In primo luogo, è stato introdotto un semplice esempio per acquisire familiarità con le operazioni standard delle lavagne. Successivamente, sono stati esaminati in modo dettagliato la lavagna e come leggere e scrivere dati da esso. Infine, è stato esaminato l'uso di un tipo di dati personalizzato per supportare la copia e incolla di tipi di dati complessi all'interno di un'app.