Sdílet prostřednictvím


Kopírování a vkládání v Xamarin.Mac

Tento článek popisuje práci s pasteboardem, který poskytuje kopírování a vkládání v aplikaci Xamarin.Mac. Ukazuje, jak pracovat se standardními datovými typy, které je možné sdílet mezi více aplikacemi a jak podporovat vlastní data v rámci dané aplikace.

Přehled

Při práci s jazykem C# a .NET v aplikaci Xamarin.Mac máte přístup ke stejné podpoře vkládání (kopírování a vkládání), kterou má vývojář pracující Objective-C .

V tomto článku probírajíme dva hlavní způsoby použití pasteboardu v aplikaci Xamarin.Mac:

  1. Standardní datové typy – protože operace pasteboardu se obvykle provádějí mezi dvěma nesouvisejícími aplikacemi, ani aplikace nezná typy dat, která druhá podporuje. Aby se maximalizoval potenciál pro sdílení, může vložit tabule obsahovat více reprezentací dané položky (pomocí standardní sady běžných datových typů), což umožňuje, aby spotřebová aplikace vybrala verzi, která je pro její potřeby nejvhodnější.
  2. Vlastní data – Pokud chcete podporovat kopírování a vkládání složitých dat v Xamarin.Mac, můžete definovat vlastní datový typ, který bude zpracovávat pasteboard. Například aplikace vektorového kreslení, která uživateli umožňuje kopírovat a vkládat složité obrazce, které se skládají z více datových typů a bodů.

Příklad spuštěné aplikace

V tomto článku se podíváme na základy práce s pasteboardem v aplikaci Xamarin.Mac, která podporuje operace kopírování a vkládání. Důrazně doporučujeme, abyste si nejprve prošli článek Hello, Mac , konkrétně úvod do Xcode a Tvůrce rozhraní a výstupy a akce , protože se zabývá klíčovými koncepty a technikami, které budeme používat v tomto článku.

Možná se budete chtít podívat také na třídy a metody v jazyce C#, které jsou uvedeny v Objective-C dokumentu Xamarin.Mac Internals , vysvětluje RegisterExport a atributy používané k připojení tříd jazyka C# k Objective-C objektům a prvkům uživatelského rozhraní.

Začínáme s pasteboardem

Pasteboard představuje standardizovaný mechanismus výměny dat v rámci dané aplikace nebo mezi aplikacemi. Typickým použitím pasteboardu v aplikaci Xamarin.Mac je zpracování operací kopírování a vkládání, ale podporuje se také řada dalších operací (například drag & Drop a Application Services).

Abychom vás rychle dostali ze země, začneme jednoduchým praktickým úvodem k používání pasteboardů v aplikaci Xamarin.Mac. Později vám poskytneme podrobné vysvětlení fungování pasteboardu a použitých metod.

V tomto příkladu vytvoříme jednoduchou aplikaci založenou na dokumentech, která spravuje okno obsahující zobrazení obrázku. Uživatel bude moct kopírovat a vkládat obrázky mezi dokumenty v aplikaci a z jiných aplikací nebo z několika oken uvnitř stejné aplikace.

Vytvoření projektu Xamarin

Nejprve vytvoříme novou aplikaci Xamarin.Mac založenou na dokumentu, pro kterou budeme přidávat podporu kopírování a vkládání.

Postupujte následovně:

  1. Spusťte Visual Studio pro Mac a klikněte na odkaz Nový projekt...

  2. Vyberte Mac>App Cocoa App> a pak klikněte na tlačítko Další:

    Vytvoření nového projektu aplikace Cocoa

  3. Zadejte MacCopyPaste název projektu a ponechte všechno ostatní jako výchozí. Klikněte na Další:

    Nastavení názvu projektu

  4. Klikněte na tlačítko Vytvořit :

    Potvrzení nastavení nového projektu

Přidání NSDocument

Dále přidáme vlastní NSDocument třídu, která bude fungovat jako úložiště na pozadí pro uživatelské rozhraní aplikace. Bude obsahovat jedno zobrazení obrázku a vědět, jak zkopírovat obrázek ze zobrazení do výchozího pasteboardu a jak pořídit obrázek z výchozího pasteboardu a zobrazit ho v zobrazení obrázku.

Pravým tlačítkem myši klikněte na projekt Xamarin.Mac na panelu řešení a vyberte Přidat>nový soubor..:

Přidání NSDocument do projektu

Zadejte ImageDocument název a klikněte na tlačítko Nový. Upravte třídu ImageDocument.cs a udělejte ji takto:

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

Pojďme se podrobněji podívat na některý z níže uvedených kódů.

Následující kód poskytuje vlastnost pro otestování existence dat obrázku na výchozí pasteboard, pokud je k dispozici image, true je vrácena jinak 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);
    }
}

Následující kód zkopíruje obrázek z připojeného zobrazení obrázku do výchozího pasteboardu:

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

}

Následující kód vloží obrázek z výchozího pasteboardu a zobrazí ho v připojeném zobrazení obrázku (pokud pasteboard obsahuje platný obrázek):

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

V tomto dokumentu vytvoříme uživatelské rozhraní pro aplikaci Xamarin.Mac.

Sestavení uživatelského rozhraní

Dvakrát klikněte na soubor Main.storyboard a otevřete ho v Xcode. Dále přidejte panel nástrojů a dobře obrázek a nakonfigurujte je následujícím způsobem:

Úprava panelu nástrojů

Přidejte kopii a vložte položku panelu nástrojů Obrázek na levou stranu panelu nástrojů. Použijeme je jako zástupce ke zkopírování a vložení z nabídky Upravit. Dále přidejte na pravou stranu panelu nástrojů čtyři položky panelu nástrojů obrázku. Použijeme je k naplnění obrázku některými výchozími obrázky.

Další informace o práci s panely nástrojů najdete v dokumentaci k panelům nástrojů .

V dalším kroku zveřejníme následující výstupy a akce pro naše položky panelu nástrojů a dobře obrázek:

Vytváření výstupů a akcí

Další informace o práci s zásuvkami a akcemi najdete v části Výstupy a akce v naší dokumentaci k Hello, Mac .

Povolení uživatelského rozhraní

S uživatelským rozhraním vytvořeným v Xcode a elementem uživatelského rozhraní vystaveným prostřednictvím zásuvek a akcí musíme přidat kód pro povolení uživatelského rozhraní. Poklikejte na soubor ImageWindow.cs v oblasti řešení a nastavte ho takto:

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

Pojďme se podrobněji podívat na tento kód níže.

Nejprve zveřejníme instanci ImageDocument třídy, kterou jsme vytvořili výše:

private ImageDocument _document;
...

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

Pomocí a ExportWillChangeValueDidChangeValuejsme nastavili Document vlastnost tak, aby umožňoval kódování klíč-hodnota a datové vazby v Xcode.

Image z obrázku, který jsme přidali do uživatelského rozhraní v Xcode, zveřejníme také s následující vlastností:

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

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

Při načtení a zobrazení hlavního okna vytvoříme instanci naší ImageDocument třídy a připojíme k němu image uživatelského rozhraní pomocí následujícího kódu:

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

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

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

Nakonec v reakci na uživatele, který klikne na kopii a vloží položky panelu nástrojů, zavoláme instanci ImageDocument třídy, která provede skutečnou práci:

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

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

Povolení nabídek Soubor a Úpravy

Poslední věcí, kterou musíme udělat, je povolit položku nabídky Nový z nabídky Soubor (vytvořit nové instance našeho hlavního okna) a povolit položky nabídky Vyjmout, Kopírovat a Vložit z nabídky Upravit .

Chcete-li povolit položku nabídky Nový , upravte soubor AppDelegate.cs a přidejte následující kód:

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

Další informace najdete v části Práce s více systémy Windows v naší dokumentaci k Windows .

Pokud chcete povolit položky nabídky Vyjmout, Kopírovat a Vložit , upravte soubor AppDelegate.cs a přidejte následující kód:

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

Pro každou položku nabídky získáme aktuální, nejvíce horní, klíčové okno a přetypujeme ho do naší ImageWindow třídy:

var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

Odtud voláme ImageDocument instanci třídy tohoto okna pro zpracování akcí kopírování a vložení. Příklad:

window.Document.CopyImage (sender);

Chceme, aby položky nabídky Vyjmout, Kopírovat a Vložit byly přístupné jenom v případě, že jsou na výchozí tabuli pro vložení nebo v obrázku v aktuálním aktivním okně data obrázku.

Pojďme do projektu Xamarin.Mac přidat soubor EditMenuDelegate.cs a nastavit, aby vypadal takto:

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

Opět získáme aktuální hlavní okno a pomocí instance třídy ImageDocument zjistíme, jestli požadovaná data obrázku existují. Pak tuto metodu MenuWillHighlightItem použijeme k povolení nebo zakázání každé položky na základě tohoto stavu.

Upravte soubor AppDelegate.cs a nastavte metodu DidFinishLaunching tak, aby vypadala takto:

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

Nejprve zakážeme automatické povolení a zakázání položek nabídky v nabídce Upravit. Dále připojíme instanci EditMenuDelegate třídy, kterou jsme vytvořili výše.

Další informace najdete v naší dokumentaci k nabídkám .

Testování aplikace

Se vším, co je na místě, jsme připraveni aplikaci otestovat. Sestavte a spusťte aplikaci a zobrazí se hlavní rozhraní:

Spouštění aplikace.

Pokud otevřete nabídku Upravit, všimněte si, že příkaz Vyjmout, Kopírovat a Vložit je zakázaný, protože na obrázku není žádný obrázek nebo ve výchozím pasteboardu:

Otevření nabídky Upravit

Pokud do dobře obrázku přidáte obrázek a znovu otevřete nabídku Upravit, budou teď povolené položky:

Zobrazení položek nabídky Upravit jsou povolené.

Pokud obrázek zkopírujete a v nabídce soubor vyberete Nový , můžete tento obrázek vložit do nového okna:

Vložení obrázku do nového okna

V následujících částech se podíváme na práci s pasteboardem v aplikaci Xamarin.Mac.

Informace o pasteboardu

V macOS (dříve označované jako OS X) pasteboard (NSPasteboard) poskytuje podporu pro několik serverových procesů, jako je kopírování a vložení, přetažení a aplikační služby. V následujících částech se podrobněji podíváme na několik konceptů vkládání klíčů.

Co je pasteboard?

Třída NSPasteboard poskytuje standardizovaný mechanismus výměny informací mezi aplikacemi nebo v rámci dané aplikace. Hlavní funkcí pasteboardu je zpracování operací kopírování a vkládání:

  1. Když uživatel vybere položku v aplikaci a použije položku nabídky Vyjmout nebo Kopírovat , umístí se na vloženou desku jedna nebo více reprezentací vybrané položky.
  2. Když uživatel použije položku nabídky Vložit (v rámci stejné aplikace nebo jiné aplikace), zkopíruje se verze dat, která může zpracovat, zkopírovaná z pasteboardu a přidá se do aplikace.

Mezi méně běžné použití pasteboardu patří operace hledání, přetažení, přetažení a aplikačních služeb:

  • Když uživatel zahájí operaci přetažení, data přetažení se zkopírují do pasteboardu. Pokud operace přetažení končí přetažením do jiné aplikace, zkopíruje tato aplikace data z pasteboardu.
  • V případě překladatelské služby se data, která se mají přeložit, zkopírují do pasteboardu požadovanou aplikací. Aplikační služba načte data z pasteboardu, provede překlad a pak vloží data zpět do pasteboardu.

V nejjednodušší podobě se pasteboardy používají k přesouvání dat uvnitř dané aplikace nebo mezi aplikacemi a existují v speciální globální oblasti paměti mimo proces aplikace. I když jsou koncepty pasteboardů snadno uchopitelné, je potřeba zvážit několik složitějších podrobností. Tyto informace jsou podrobně popsány níže.

Pojmenované pasteboardy

Pasteboard může být veřejný nebo soukromý a může se používat pro různé účely v rámci aplikace nebo mezi několika aplikacemi. macOS poskytuje několik standardních pasteboardů, z nichž každá má specifické dobře definované použití:

  • NSGeneralPboard – Výchozí pasteboard pro operace Vyjmutí, kopírování a vložení .
  • NSRulerPboard - Podporuje operace Vyjmutí, kopírování a vkládání na pravítkách.
  • NSFontPboard - Podporuje operace vyjmutí, kopírování a vložení u NSFont objektů.
  • NSFindPboard - Podporuje panely hledání specifické pro aplikaci, které můžou sdílet hledaný text.
  • NSDragPboard - Podporuje operace přetažení .

Ve většině situací použijete jednu ze systémově definovaných pasteboardů. Můžou ale nastat situace, které vyžadují, abyste vytvořili vlastní vložené desky. V těchto situacích můžete pomocí FromName (string name) metody NSPasteboard třídy vytvořit vlastní pasteboard s daným názvem.

Volitelně můžete volat metodu CreateWithUniqueNameNSPasteboard třídy k vytvoření jedinečně pojmenované pasteboard.

Položky pasteboardu

Každá data, která aplikace zapisuje do pasteboardu, se považuje za položku pasteboardu a pasteboard může obsahovat více položek najednou. Aplikace tak může zapisovat více verzí dat zkopírovaných do pasteboardu (například prostý text a formátovaný text) a aplikace pro načítání může číst jenom data, která může zpracovávat (například jenom prostý text).

Reprezentace dat a identifikátory uniformního typu

Operace pasteboardu se obvykle provádí mezi dvěma (nebo více) aplikacemi, které nemají žádné znalosti o sobě nebo o typech dat, která každý dokáže zpracovat. Jak je uvedeno v předchozí části, aby se maximalizoval potenciál sdílení informací, může pasteboard obsahovat více reprezentací zkopírovaných a vložených dat.

Každá reprezentace se identifikuje prostřednictvím identifikátoru UTI (Uniform Type Identifier), což není nic víc než jednoduchý řetězec, který jednoznačně identifikuje typ prezentovaného data (další informace najdete v dokumentaci k přehledu identifikátorů jednotného typu společnosti Apple).

Pokud vytváříte vlastní datový typ (například nakreslený objekt v aplikaci pro kreslení vektorů), můžete vytvořit vlastní UTI, abyste ho jedinečně identifikovali při operacích kopírování a vkládání.

Když se aplikace připraví na vložení dat zkopírovaných z pasteboardu, musí najít reprezentaci, která nejlépe vyhovuje jeho schopnostem (pokud existuje). Obvykle se jedná o nejbohatší dostupný typ (například formátovaný text pro aplikaci pro zpracování textu), který se vrátí k nejjednodušším formulářům dostupným podle potřeby (prostý text pro jednoduchý textový editor).

Slibovaná data

Obecně řečeno, měli byste poskytnout co nejvíce reprezentací zkopírovaných dat, abyste maximalizovali sdílení mezi aplikacemi. Z důvodu omezení času nebo paměti ale může být nepraktické zapisovat každý datový typ do pasteboardu.

V této situaci můžete umístit první reprezentaci dat na pasteboard a přijímající aplikace může požádat o jinou reprezentaci, která se dá vygenerovat za běhu těsně před operací vložení.

Když umístíte počáteční položku do pasteboardu, určíte, že jeden nebo více dalších dostupných reprezentací je poskytováno objektem, který odpovídá NSPasteboardItemDataProvider rozhraní. Tyto objekty budou poskytovat dodatečné reprezentace na vyžádání, jak požaduje přijímající aplikace.

Počet změn

Každý pasteboard udržuje počet změn, který se zvýší pokaždé, když je deklarován nový vlastník. Aplikace může zjistit, jestli se obsah vložené desky od posledního prozkoumání změnil, a to tak, že zkontroluje hodnotu počtu změn.

ChangeCount Pomocí a ClearContents metod NSPasteboard třídy upravte zadaný typ pasteboard Change Count.

Kopírování dat do pasteboardu

Operaci kopírování provedete tak, že nejprve přistupujete k pasteboardu, vymažete veškerý existující obsah a zapíšete tolik reprezentací dat, kolik potřebujete pro vložení.

Příklad:

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

Obvykle jen píšete na obecnou pasteboard, jak jsme to udělali v předchozím příkladu. Jakýkoli objekt, který odešlete do WriteObjects metody , musí odpovídat INSPasteboardWriting rozhraní. Několik předdefinovaných tříd (například NSString, , NSImageNSURLNSColor, , NSAttributedString, a NSPasteboardItem) automaticky odpovídá tomuto rozhraní.

Pokud do pasteboardu píšete vlastní datovou třídu, musí být v souladu s INSPasteboardWriting rozhraním nebo musí být zabalena v instanci NSPasteboardItem třídy (viz část Vlastní datové typy níže).

Čtení dat z pasteboardu

Jak je uvedeno výše, aby se maximalizoval potenciál sdílení dat mezi aplikacemi, může být do pasteboardu zapsáno více reprezentací zkopírovaných dat. Je na přijímající aplikaci, aby pro své schopnosti vybrali nejbohatší možnou verzi (pokud existuje).

Jednoduchá operace vložení

Data z pasteboardu můžete číst pomocí ReadObjectsForClasses metody. Bude vyžadovat dva parametry:

  1. Pole typů založených NSObject na třídách, které chcete číst z pasteboardu. Měli byste ho nejprve uspořádat s nejžádnějším datovým typem, přičemž všechny zbývající typy se snižující předvolbou.
  2. Slovník obsahující další omezení (například omezení na konkrétní typy obsahu adresy URL) nebo prázdný slovník, pokud nejsou vyžadována žádná další omezení.

Metoda vrátí pole položek, které splňují kritéria, která jsme předali, a proto obsahuje maximálně stejný počet datových typů, které jsou požadovány. Je také možné, že žádný z požadovaných typů není k dispozici a vrátí se prázdné pole.

Následující kód například zkontroluje, jestli NSImage existuje v obecném pasteboardu, a pokud ano, zobrazí ho na obrázku dobře:

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

Vyžádání více datových typů

Na základě typu vytvářené aplikace Xamarin.Mac může být schopen zpracovat více reprezentací vložených dat. V této situaci existují dva scénáře načítání dat z pasteboardu:

  1. Proveďte jedno volání ReadObjectsForClasses metody a zadejte pole všech reprezentací, které chcete (v upřednostňovaném pořadí).
  2. Proveďte více volání ReadObjectsForClasses metody s žádostí o různé pole typů pokaždé.

Další podrobnosti o načítání dat z pasteboardu najdete v části Operace jednoduchého vložení výše.

Kontrola existujících datových typů

Někdy můžete chtít zkontrolovat, jestli pasteboard obsahuje danou datovou reprezentaci, aniž byste skutečně četli data z pasteboardu (například povolení položky nabídky Vložit pouze v případě, že existují platná data).

CanReadObjectForClasses Zavolejte metodu pasteboardu, abyste zjistili, jestli obsahuje daný typ.

Například následující kód určuje, jestli obecná pasteboard obsahuje NSImage instanci:

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

Čtení adres URL z pasteboardu

Na základě funkce dané aplikace Xamarin.Mac může být nutné číst adresy URL z pasteboardu, ale pouze v případě, že splňují danou sadu kritérií (například odkazování na soubory nebo adresy URL konkrétního datového typu). V takovém případě můžete pomocí druhého parametru CanReadObjectForClasses nebo ReadObjectsForClasses metod zadat další kritéria hledání.

Vlastní datové typy

Někdy budete muset uložit vlastní typy do pasteboardu z aplikace Xamarin.Mac. Například aplikace vektorového kreslení, která uživateli umožňuje kopírovat a vkládat nakreslené objekty.

V této situaci budete muset navrhnout vlastní třídu dat tak, aby dědila NSObject a odpovídala několika rozhraním (INSCodingINSPasteboardWritingaINSPasteboardReading). Volitelně můžete použít NSPasteboardItem k zapouzdření dat, která se mají zkopírovat nebo vložit.

Obě tyto možnosti jsou podrobně popsány níže.

Použití vlastní třídy

V této části rozšíříme jednoduchou ukázkovou aplikaci, kterou jsme vytvořili na začátku tohoto dokumentu, a přidáme vlastní třídu pro sledování informací o obrázku, který kopírujeme a vkládáme mezi okny.

Přidejte do projektu novou třídu a zavolejte ji ImageInfo.cs. Upravte soubor a udělejte ho takto:

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

V následujících částech se podíváme na tuto třídu.

Dědičnost a rozhraní

Před zápisem nebo čtením vlastní datové třídy z pasteboardu musí odpovídat rozhraním INSPastebaordWriting a INSPasteboardReading rozhraním. Kromě toho musí dědit z NSObject rozhraní a také odpovídat rozhraní INSCoding :

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

Třída musí být také vystavena použití Objective-C direktivy Register a musí zveřejnit všechny požadované vlastnosti nebo metody pomocí Export. Příklad:

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

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

Vystavujeme dvě pole dat, která tato třída bude obsahovat – název obrázku a jeho typ (jpg, png atd.).

Další informace najdete v části Zpřístupnění tříd a metod jazyka C# v Objective-Cdokumentaci k interním informacím o Xamarin.Mac. Vysvětluje Register atributy a Export atributy používané k připojení tříd jazyka C# k Objective-C objektům a prvkům uživatelského rozhraní.

Konstruktory

Pro naši vlastní třídu dat budou vyžadovány dva konstruktory (správně vystavené Objective-C), aby je bylo možné číst z pasteboardu:

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

Nejprve zveřejníme prázdný konstruktor pod výchozí Objective-C metodou init.

Dále zveřejníme NSCoding kompatibilní konstruktor, který se použije k vytvoření nové instance objektu z pasteboardu při vkládání pod exportovaný název initWithCoder.

Tento konstruktor přebírá NSCoder (vytvořený NSKeyedArchiver při zápisu do pasteboardu), extrahuje spárovaná data klíče/hodnoty a uloží je do polí vlastností datové třídy.

Zápis do pasteboardu

V souladu s INSPasteboardWriting rozhraním musíme vystavit dvě metody a volitelně třetí metodu, aby třída mohla být zapsána do pasteboardu.

Nejprve musíme informovat pasteboard, do jakého datového typu je možné zapsat vlastní třídu:

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

Každá reprezentace se identifikuje prostřednictvím identifikátoru UTI (Uniform Type Identifier), což není nic víc než jednoduchý řetězec, který jednoznačně identifikuje typ prezentovaných dat (další informace najdete v dokumentaci k přehledu identifikátorů jednotných typů společnosti Apple).

Pro náš vlastní formát vytváříme vlastní UTI: com.xamarin.image-info (všimněte si, že je v obráceném zápisu stejně jako identifikátor aplikace). Naše třída je také schopná napsat standardní řetězec do pasteboardu (public.text).

Dále musíme vytvořit objekt v požadovaném formátu, který se skutečně zapíše do pasteboardu:

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

Pro typ public.text vracíme jednoduchý formátovaný NSString objekt. Pro vlastní com.xamarin.image-info typ používáme NSKeyedArchiver a NSCoder rozhraní ke kódování vlastní datové třídy do párovaného archivu klíč/hodnota. Pro skutečné zpracování kódování budeme muset implementovat následující metodu:

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

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

Jednotlivé páry klíč/hodnota se zapisují do kodéru a dekódují se pomocí druhého konstruktoru, který jsme přidali výše.

Volitelně můžeme zahrnout následující metodu, která definuje všechny možnosti při zápisu dat do pasteboardu:

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

V současné době je k dispozici pouze WritingPromised možnost a měla by se použít, pokud je daný typ pouze slibován a ve skutečnosti není zapsán do pasteboardu. Další informace najdete v části Slíbená data výše.

S těmito metodami je možné k zápisu vlastní třídy do pasteboardu použít následující kód:

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

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

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

Čtení z pasteboardu

V souladu s INSPasteboardReading rozhraním musíme zveřejnit tři metody, aby vlastní datová třída mohla být načtena z pasteboardu.

Nejprve musíme informovat pasteboard, jaké datové typy znázorňují, že vlastní třída může číst ze schránky:

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

Opět jsou definovány jako jednoduché UTI a jsou stejné typy, které jsme definovali v části Zápis do pasteboardu výše.

Dále musíme informovat pasteboard , jak se jednotlivé typy UTI budou číst pomocí následující metody:

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

com.xamarin.image-info U typu říkáme, že pasteboard dekóduje dvojici klíč/hodnota, kterou jsme vytvořili při NSKeyedArchiver zápisu třídy do pasteboardu zavoláním konstruktoruinitWithCoder:, který jsme přidali do třídy.

Nakonec musíme přidat následující metodu pro čtení ostatních reprezentací dat UTI z pasteboardu:

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

Se všemi těmito metodami lze vlastní datovou třídu číst z pasteboardu pomocí následujícího kódu:

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

Použití NSPasteboardItem

Můžou se stát, že budete muset na vložit tabuli napsat vlastní položky, které neopravňují vytvoření vlastní třídy nebo chcete poskytnout data v běžném formátu, pouze podle potřeby. V těchto situacích můžete použít .NSPasteboardItem

Poskytuje NSPasteboardItem jemně odstupňovanou kontrolu nad daty, která jsou zapsána do pasteboardu a jsou navržena pro dočasný přístup – měla by být odstraněna po zápisu do pasteboardu.

Zápis dat

Pokud chcete napsat vlastní data do vlastního objektu NSPasteboardItem , budete muset zadat vlastní NSPasteboardItemDataProvider. Přidejte do projektu novou třídu a zavolejte ji ImageInfoDataProvider.cs. Upravte soubor a udělejte ho takto:

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

Stejně jako u vlastní datové třídy potřebujeme použít direktivy Register a Export direktivy k jeho zveřejnění Objective-C. Třída musí dědit z NSPasteboardItemDataProvider a musí implementovat FinishedWithDataProvider a ProvideDataForType metody.

ProvideDataForType Pomocí metody zadejte data, která budou zabalena následujícím NSPasteboardItem způsobem:

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

}

V tomto případě ukládáme dvě informace o našem obrázku (Name a ImageType) a zapisujeme je do jednoduchého řetězce (public.text).

Zadejte data do pasteboardu a použijte následující kód:

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

Čtení dat

Pokud chcete číst data zpět z pasteboardu, použijte následující kód:

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

Shrnutí

Tento článek se podrobně podíval na práci s pasteboardem v aplikaci Xamarin.Mac, která podporuje operace kopírování a vkládání. Nejprve se představil jednoduchý příklad, abyste se seznámili se standardními operacemi vkládání. Dále se podrobně podíval na vloženou desku a na to, jak z ní číst a zapisovat data. Nakonec jsme se podívali na použití vlastního datového typu, který podporuje kopírování a vkládání složitých datových typů v aplikaci.