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:

  1. 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.
  2. 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.

Example of the running app

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:

  1. Avviare Visual Studio per Mac e fare clic sul collegamento Nuovo progetto.

  2. Selezionare Mac>App Cocoa App> e quindi fare clic sul pulsante Avanti:

    Creating a new Cocoa app project

  3. Immettere MacCopyPaste per Nome progetto e mantenere tutto il resto come predefinito. Fare clic su Avanti:

    Setting the name of the project

  4. Fare clic sul pulsante Crea :

    Confirming the new project settings

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

Adding an NSDocument to the project

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:

Editing the toolbar

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:

Creating outlets and actions

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:

Running the application

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:

Opening the Edit menu

Se si aggiunge un'immagine all'immagine e si riapre il menu Modifica, le voci verranno ora abilitate:

Showing the Edit menu items are enabled

Se si copia l'immagine e si seleziona Nuovo dal menu file, è possibile incollare l'immagine nella nuova finestra:

Pasting an image into a new window

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:

  1. 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.
  2. 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 sugli NSFont 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, NSColorNSAttributedStringNSImageNSURLe 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:

  1. 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.
  2. 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:

  1. Effettuare una singola chiamata al ReadObjectsForClasses metodo e fornire una matrice di tutte le rappresentazioni desiderate (nell'ordine preferito).
  2. 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 (INSCodingINSPasteboardWritinge 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 NSKeyedArchiverNSCoder 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.