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

Tento článek se věnuje práci s vložením a kopírováním a vkládáním do aplikace 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 C# a .NET v aplikaci Xamarin.Mac máte přístup ke stejné podpoře vložení (kopírování a vkládání), ve které pracuje Objective-C vývojář.

V tomto článku probýme dva hlavní způsoby použití tohoto vložení v aplikaci Xamarin.Mac:

  1. Standardní datové typy – Vzhledem k tomu, že se operace vložení obvykle provádějí mezi dvěma nesouvisejícími aplikacemi, žádná aplikace nezná typy dat, které podporuje druhá aplikace. Aby se maximalizoval potenciál sdílení, může vložení obsahovat více reprezentací dané položky (pomocí standardní sady běžných datových typů), které aplikaci umožňují vybrat verzi, která je pro její potřeby nejlepší.
  2. Vlastní data – Pro podporu kopírování a vkládání složitých dat v rámci Xamarin.Mac můžete definovat vlastní datový typ, který bude pracovní panel zpracovat. Například aplikace pro vektorové kreslení, která uživateli umožňuje kopírovat a vkládat složité tvary, které se skládají z více datových typů a bodů.

Příklad spuštěné aplikace

V tomto článku se budeme seznamovat se 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ě oddíly Úvod do Xcode a Interface Builder a Výstupy a Akce, protože se zabývá klíčovými koncepty a technikami, které budeme v tomto článku používat.

Možná se budete chtít podívat i na část dokumentu Exposing C# classes / methods to Objective-CExposing C# classes / methods to Objective-C která vysvětluje atributy a používané k připojení tříd jazyka C# k objektům a prvkům uživatelského RegisterExportObjective-C rozhraní.

Začínáme s vložením

Vložení představuje standardizovaný mechanismus pro výměnu dat v rámci dané aplikace nebo mezi aplikacemi. Vložení v aplikaci Xamarin.Mac se obvykle používá ke zpracování operací kopírování a vložení, ale podporuje se i řada dalších operací (například přetažení a & aplikační služby).

Pro rychlé odlesky začneme jednoduchým praktickým úvodem do používání pasteboardů v aplikaci Xamarin.Mac. Později poskytneme podrobné vysvětlení toho, jak pracovní panel funguje a jaké metody se používají.

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

Vytvoření projektu Xamarin

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

Postupujte následovně:

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

  2. Vyberte MacAppCocoa Appa pak klikněte na tlačítko Další:

    Vytvoření nového projektu aplikace Cocoa:

  3. Jako MacCopyPaste název Project zadejte MacCopyPaste jako výchozí podržte všechno ostatní. Klikněte na Další:

    Nastavení názvu projektu Nastavení

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

    Potvrzení nastavení nového projektu

Přidání dokumentu NSDocument

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

Klikněte pravým tlačítkem na projekt Xamarin.Mac v okně Oblast řešenívyberte Přidatnový soubor.:

Přidání NSDocument do projektu

Jako ImageDocument Název zadejte ImageDocument klikněte na tlačítko Nový. Upravte třídu ImageDocument.cs, aby vypadala 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
    }
}

Podívejme se na část kódu podrobněji níže.

Následující kód poskytuje vlastnost k otestování existence dat obrázku na výchozím pasteboardu, pokud je obrázek k dispozici, true se vrátí 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 });
        }
    }

}

A následující kód vloží obrázek z výchozího vložení a zobrazí ho v připojeném zobrazení obrázku (pokud pracovní panel 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]
    }
}

Po vytvoření tohoto dokumentu vytvoříme uživatelské rozhraní pro aplikaci Xamarin.Mac.

Vytvoření uživatelského rozhraní

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

Úprava panelu nástrojů

Přidejte položku panelu nástrojů Kopírovat a vložit obrázek na levou stranu panelu nástrojů. Tyto klávesové zkratky budeme používat ke kopírování a vkládání z nabídky Upravit. Pak na pravou stranu panelu nástrojů přidejte čtyři položky panelu nástrojů Obrázek. Použijeme je k naplnění image některými výchozími imagemi.

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

V dalším kroku zpřístupníme následující výstupy a akce pro položky panelu nástrojů a obrázek:

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

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

Povolení uživatelského rozhraní

Když máme uživatelské rozhraní vytvořené v Xcode a prvek uživatelského rozhraní vystavený prostřednictvím výstupů a akcí, musíme přidat kód, který uživatelské rozhraní povolí. Poklikejte na soubor ImageWindow.cs v Oblast řešení, aby vypadal 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
    }
}

Podívejme se na tento kód podrobněji níže.

Nejprve zveřejňujeme 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 jsme na nastavení vlastnosti umožnili kódování kódu Key-Value datové ExportWillChangeValue vazby v DidChangeValueDocument Xcode.

Obrázek také zveřejňujeme z image, který jsme přidali do našeho uživatelského rozhraní v Xcode, 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 třídy a připojíme k ní image uživatelského rozhraní pomocí ImageDocument 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 to, že uživatel klikne na položky panelu nástrojů pro kopírování a vkládání, zavoláme instanci třídy , aby ImageDocument skutečně pracoval:

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

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

Povolení nabídek Soubor a Upravit

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

Pokud chcete 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 Windows v naší Windows dokumentaci.

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áte aktuální okno klíče, které je na nejvyšším místě, a přetypováme ho na naši ImageWindow třídu:

var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

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

window.Document.CopyImage (sender);

Chceme, aby byly položky nabídky Vyjmout, Kopírovat a Vložit přístupné jenom v případě, že jsou ve výchozím vkládání nebo v imutu obrázku aktuálního aktivního okna data obrázků.

Pojďme do projektu Xamarin.Mac přidat soubor EditMenuDelegate.cs a vypadat 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 dostaneme aktuální okno nejvyšší kategorie a pomocí instance třídy uvidíme, jestli požadovaná ImageDocument data image existují. Pak pomocí metody MenuWillHighlightItem povolíme nebo zakážeme každou položku na základě tohoto stavu.

Upravte soubor AppDelegate.cs a zajistěte, aby metoda 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 třídy EditMenuDelegate , kterou jsme vytvořili výše.

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

Otestování aplikace

Když máte všechno připravené, můžeme aplikaci otestovat. Sestavte a spusťte aplikaci a zobrazí se hlavní rozhraní:

Spuštění aplikace

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

Otevření nabídky Upravit

Pokud do image přidáte obrázek a znovu otevřete nabídku Upravit, položky se teď povolí:

Zobrazení povolených položek nabídky Upravit zobrazující

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

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

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

Informace o vložení

V systému macOS (dříve OS X) poskytuje vložení ( ) podporu několika serverových procesů, jako je kopírování NSPasteboard& vložení, přetažení a & aplikační služby. V následujících částech se blíže podíváme na několik klíčových konceptů vkládání.

Co je pracovní panel?

Třída poskytuje standardizovaný mechanismus pro výměnu informací mezi NSPasteboard aplikacemi nebo v rámci dané aplikace. Hlavní funkcí pracovní plochy je pro zpracování operací kopírování a vložení:

  1. Když uživatel vybere položku v aplikaci a použije položku nabídky Vyjmout nebo Kopírovat , jedno nebo více reprezentace vybrané položky se umístí na pracovní plochu.
  2. Když uživatel použije položku nabídky Vložit (v rámci stejné aplikace nebo jiné), verze dat, kterou může zpracovat, se zkopíruje z pracovní plochy a přidá do aplikace.

Méně zřejmá pracovní plocha používá operace najít, přetáhnout, přetáhnout a aplikační služby:

  • Když uživatel zahájí operaci přetažení, data přetažení se zkopírují na pracovní plochu. Pokud operace přetažení skončí s přesunutím do jiné aplikace, aplikace zkopíruje data z pracovní plochy.
  • Pro překladatelské služby se data, která mají být přeložena, zkopírují na pracovní plochu tím, že aplikace požaduje. Aplikační služba, načte data z pracovní plochy, provede překlad a pak vloží data zpátky na pracovní plochu.

V nejjednodušším tvaru se pracovní plochy používají k přesunu dat v dané aplikaci nebo mezi aplikacemi a existují ve speciální globální oblasti paměti mimo proces aplikace. I když jsou koncepty pracovní plochy snadno uchopit, existuje několik složitějších podrobností, které je třeba zvážit. Na tyto podrobnosti se dozvíte podrobněji.

Pojmenované pracovní plochy

Pracovní plocha může být veřejná nebo soukromá a může se používat pro nejrůznější účely v rámci aplikace nebo mezi několika aplikacemi. macOS poskytuje několik standardních pracovní ploch, z nichž každá má konkrétní, jasně definované použití:

  • NSGeneralPboard – Výchozí pracovní plocha pro operace NSGeneralPboard, kopírování a vložení .
  • NSRulerPboard – Podporuje operace NSRulerPboard, kopírování a vložení na pravítkech.
  • NSFontPboard – Podporuje operace NSFontPboard, kopírování a vložení u objektů.
  • NSFindPboard – Podporuje panely hledání specifické pro aplikaci, které mohou sdílet hledaný text.
  • NSDragPboard-Podporuje NSDragPboard

Ve většině případů použijete jednu z definovaných systémových ploch. Mohou však nastat situace, kdy budete potřebovat vytvořit vlastní pracovní plochu. V těchto situacích lze pomocí FromName (string name) metody NSPasteboard třídy vytvořit vlastní pracovní plochu s daným názvem.

Volitelně můžete zavolat CreateWithUniqueName metodu NSPasteboard třídy a vytvořit tak jedinečnou pojmenovanou pracovní plochu.

Položky pracovní plochy

Každá část dat, kterou aplikace zapisuje na pracovní plochu, je považována za položku pracovní plochy a pracovní plocha může obsahovat více položek současně. Tímto způsobem může aplikace zapisovat více verzí dat kopírovaných na pracovní plochu (například prostý text a formátovaný text) a načítání aplikace může číst pouze data, která může zpracovat (například pouze prostý text).

Reprezentace dat a identifikátory uniformního typu

K operacím pracovní plochy obvykle dochází mezi dvěma (nebo více) aplikacemi bez jakýchkoli znalostí nebo typů dat, která může každý z nich zpracovat. Jak je uvedeno v části výše, aby bylo možné maximalizovat možnosti sdílení informací, může pracovní plocha obsahovat více reprezentace kopírovaných a vkládaných dat.

Každá reprezentace je identifikována prostřednictvím identifikátoru Uniform Type (identifikátor UTI), který není větší než jednoduchý řetězec, který jednoznačně identifikuje typ data, který se zobrazí (Další informace najdete v dokumentaci k přehledu jednotných typů identifikátorů ) společnosti Apple.

Pokud vytváříte vlastní datový typ (například nakreslený objekt v aplikaci Vector Drawing), můžete vytvořit vlastní identifikátor UTI a jednoznačně ho identifikovat v operacích kopírování a vložení.

Když se aplikace připraví, aby vložila data zkopírovaná z pracovní plochy, musí najít reprezentace, která nejlépe odpovídá jejich schopnostem (pokud existuje). Obvykle se jedná o dostupný typ, který je k dispozici (například formátovaný text pro aplikaci pro zpracování textu), návrat k nejjednodušším formulářům dostupným podle potřeby (prostý text v jednoduchém textovém editoru).

Přislíbená data

Obecně řečeno, měli byste zadat tolik reprezentace dat, která se mají kopírovat, aby bylo možné maximalizovat sdílení mezi aplikacemi. Vzhledem k omezením času nebo paměti ale může být nepraktické ve skutečnosti zapisovat každý datový typ do pracovní plochy.

V této situaci můžete první datovou prezentaci umístit na pracovní plochu a přijímající aplikace si může vyžádat jinou reprezentaci, kterou je možné vygenerovat těsně před operací vložení.

Když umístíte počáteční položku na pracovní plochu, určíte, že jeden nebo více dostupných dalších reprezentace je zajiště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á pracovní plocha udržuje počet změn , který se zvýší při každé deklaraci nového vlastníka. Aplikace může určit, jestli se obsah pracovní plochy od posledního zkoumání změnil, a to tak, že zkontroluje hodnotu počet změn.

Použijte ChangeCount metody a ClearContentsNSPasteboard třídy pro úpravu počtu změn dané pracovní plochy.

Kopírování dat na pracovní plochu

Operaci kopírování provedete tak, že nejprve přistupujete k pracovní ploše a vymažete stávající obsah a zapisujete tolik reprezentace dat, kolik je potřeba pro pracovní plochu.

Napří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 budete psát jenom na plochu General, jak jsme to udělali v příkladu výše. Libovolný objekt, který odesíláte do WriteObjects metody, WriteObjects odpovídat INSPasteboardWriting rozhraní. Několik vestavěných tříd (například,,, NSString , NSImageNSURLNSColorNSAttributedString a NSPasteboardItem ) automaticky odpovídají tomuto rozhraní.

Pokud píšete vlastní datovou třídu na pracovní plochu, musí odpovídat INSPasteboardWriting rozhraní nebo být zabalena do instance NSPasteboardItem třídy (viz část INSPasteboardWriting ).

Čtení dat z pracovní plochy

Jak je uvedeno výše, k maximalizaci potenciálu pro sdílení dat mezi aplikacemi se můžou na pracovní plochu zapsat víc reprezentace kopírovaných dat. Je k dispozici až přijímající aplikace, která vybere bohatou verzi, která je možné využít pro své možnosti (pokud existuje).

Operace jednoduchého vložení

Data z pracovní plochy načtete pomocí ReadObjectsForClasses metody. Bude vyžadovat dva parametry:

  1. Pole NSObject typů tříd založených na pracovní ploše, které chcete číst. Nejdříve byste měli tento typ seřadit s požadovaným datovým typem s případnými zbývajícími typy v klesajících preferencích.
  2. Slovník obsahující další omezení (například omezení na konkrétní typy obsahu 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á předáváme, 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í přítomen a prázdné pole bude vráceno.

Například následující kód zkontroluje, zda NSImage existuje v obecné pracovní ploše a zobrazí jej v obrazové příručce, pokud má:

[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ů

V závislosti na typu vytvářené aplikace Xamarin. Mac může být možné zpracovávat více reprezentace vkládaných dat. V této situaci existují dva scénáře načítání dat z pracovní plochy:

  1. Proveďte jedno volání ReadObjectsForClasses metody a poskytněte pole všech reprezentace, kterou si přejete (v upřednostňovaném pořadí).
  2. Pokaždé, když se ReadObjectsForClasses požádá o jiné pole typů, proveďte několik volání metody.

Další informace o načítání dat z pracovní plochy najdete v části věnované jednoduché operaci vložení výše.

Kontrola existujících datových typů

Existují situace, kdy byste měli chtít zjistit, zda pracovní plocha obsahuje danou datovou prezentaci, aniž by bylo nutné číst data z pracovní plochy (například povolení položky nabídky Vložit pouze v případě, že existují platná data).

Zavolejte CanReadObjectForClasses metodu pracovní plochy, abyste viděli, zda obsahuje daný typ.

Například následující kód určuje, zda Obecná pracovní plocha 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 pracovní plochy

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

Vlastní datové typy

K dispozici jsou situace, kdy budete potřebovat uložit vlastní typy na pracovní plochu z aplikace Xamarin. Mac. Například aplikace vektorového kreslení, která umožňuje uživateli kopírovat a vkládat objekty kresby.

V této situaci budete muset navrhnout vlastní třídu dat tak, aby dědila z NSObject a byla v souladu s několika rozhraními ( INSCodingINSPasteboardWriting a INSPasteboardReading ). Volitelně můžete použít NSPasteboardItem k zapouzdření dat, která mají být kopírována nebo vložena.

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

Použití vlastní třídy

V této části se seznámíte s jednoduchou ukázkovou aplikací, kterou jsme vytvořili na začátku tohoto dokumentu, a přidáním vlastní třídy, která sleduje informace o imagi, kterou kopírujeme a vkládáme mezi Windows.

Přidejte do projektu novou třídu a zavolejte ji IMAGEINFO. cs. Upravte soubor a nastavte jeho vzhled jako na následující:

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 podíváme se na tuto třídu podrobněji.

Dědičnost a rozhraní

Předtím, než lze vlastní datovou třídu zapsat nebo číst z pracovní plochy, musí být v souladu s INSPastebaordWritingINSPasteboardReading rozhraními a. Kromě toho musí dědit od NSObject a také splňovat INSCoding rozhraní:

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

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

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

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

Zveřejňujeme dvě pole dat, která tato třída bude obsahovat – název a typ obrázku (jpg, PNG atd.).

Další informace naleznete v Exposing C# classes / methods to Objective-C části dokumentace k Exposing C# classes / methods to Objective-C , kde jsou vysvětleny RegisterExport atributy a použité k navýšení tříd jazyka C# do Objective-C objektů a prvků uživatelského rozhraní.

Konstruktory

Objective-CPro naši vlastní datovou třídu se vyžadují dva konstruktory (správné vystavené pro), aby je bylo možné číst z pracovní plochy:

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

Nejdřív zveřejňujeme prázdný konstruktor pod výchozí metodou init .

Dále vystavíme NSCoding vyhovující konstruktor, který se použije k vytvoření nové instance objektu z pracovní plochy při vložení pod exportovaným názvem initWithCoder .

Tento konstruktor přebírá NSCoder (jak je vytvořeno NSKeyedArchiver při zápisu na pracovní plochu), extrahuje spárovaná data klíč/hodnota a uloží je do polí vlastností datové třídy.

Zápis na pracovní plochu

V souladu s INSPasteboardWriting rozhraním je nutné vystavit dvě metody a volitelně třetí metodu, aby bylo možné třídu zapsat na pracovní plochu.

Nejprve musíme tabuli říct, do jakých reprezentací 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 je identifikována prostřednictvím identifikátoru UTI (Uniform Type Identifier), což není nic jiného než jednoduchý řetězec, který jednoznačně identifikuje typ prezentovaných dat (další informace najdete v dokumentaci Přehled jednotných identifikátorů typů společnosti Apple).

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

Dále potřebujeme vytvořit objekt v požadovaném formátu, který se ve skutečnosti zapisovat 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 public.text typ vracíme jednoduchý formátovaný NSString objekt. Pro vlastní typ používáme rozhraní a ke kódování vlastní datové třídy do spárovaného archivu com.xamarin.image-infoNSKeyedArchiverNSCoder klíč/hodnota. Pro vlastní 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 zapisou do kodéru a dekóduje se pomocí druhého konstruktoru, který jsme přidali výše.

Volitelně můžeme zahrnout následující metodu, která definuje jakékoli možnosti při zápisu dat na pracovní panel:

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

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

Když jsou tyto metody k dispozici, můžete k zápisu vlastní třídy na tabuli 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 tabule

V souladu s rozhraním musíme zveřejnit tři metody, aby bylo možné vlastní třídu dat číst INSPasteboardReading z tabule.

Nejprve musíme tabuli říct, jaké reprezentace datového typu může vlastní třída číst ze schránky:

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

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

Dále musíme tabuli říct, jak se budou číst jednotlivé typy UTI, 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;
}

U typu říkáme, že má vložení dekódovat pár klíč/hodnota, který jsme vytvořili pomocí třídy při zápisu třídy do tabule voláním konstruktoru, který jsme přidali com.xamarin.image-infoNSKeyedArchiver do initWithCoder: třídy.

Nakonec musíme přidat následující metodu, která načte další reprezentace dat UTI z tabule:

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

Když jsou všechny tyto metody k dispozici, můžete pomocí následujícího kódu načíst vlastní třídu dat z tabule:

// 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 zobrazit časy, kdy potřebujete na vložení zapsat vlastní položky, které nezaručují vytvoření vlastní třídy, nebo když chcete zadat data ve společném formátu, pouze pokud je to nutné. V takových situacích můžete použít NSPasteboardItem .

Objekt poskytuje přesnou kontrolu nad daty, která jsou zapsána na pracovní panel a jsou navržena pro dočasný přístup – měla by se po zápisu na vložení NSPasteboardItem ukončovat.

Zápis dat

Pokud chcete zapsat vlastní data NSPasteboardItem do , budete muset zadat vlastní NSPasteboardItemDataProvider . Přidejte do projektu novou třídu a volejte ji ImageInfoDataProvider.cs. Upravte soubor tak, aby vypadal 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í třídy dat musíme použít direktivy a , aby RegisterExport byla přístupná pro Objective-C . Třída musí dědit z a NSPasteboardItemDataProvider musí implementovat metody a FinishedWithDataProviderProvideDataForType .

Pomocí metody ProvideDataForType zadejte data, která se zabalí NSPasteboardItem do souboru , následujícím 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 dva údaje o obrázku (Name a ImageType) a zapisujeme je do jednoduchého řetězce ( public.text ).

Napište 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 načíst data zpět z tabule, 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
    ...
}

Souhrn

Tento článek podrobně popisuje práci s vložením v aplikaci Xamarin.Mac na podpoře operací kopírování a vkládání. Nejprve se představil jednoduchý příklad, který vás seznámí se standardními operacemi s vkládáním. Dále se podrobně podíval na pracovní panel a na to, jak z něj číst a zapisovat data. Nakonec se podíval na použití vlastního datového typu pro podporu kopírování a vkládání složitých datových typů v aplikaci.