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:
- 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ší.
- 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ů.
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 Register
Export
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ě:
Spusťte Visual Studio pro Mac a klikněte na odkaz Nový projekt...
Vyberte Mac>App Cocoa App> a pak klikněte na tlačítko Další:
Zadejte
MacCopyPaste
název projektu a ponechte všechno ostatní jako výchozí. Klikněte na Další:Klikněte na tlačítko Vytvořit :
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..:
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:
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:
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 Export
WillChangeValue
DidChangeValue
jsme 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í:
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:
Pokud do dobře obrázku přidáte obrázek a znovu otevřete nabídku Upravit, budou teď povolené položky:
Pokud obrázek zkopírujete a v nabídce soubor vyberete Nový , můžete tento obrázek vložit 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í:
- 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.
- 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í uNSFont
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 CreateWithUniqueName
NSPasteboard
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
, , NSImage
NSURL
NSColor
, , 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:
- 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. - 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:
- Proveďte jedno volání
ReadObjectsForClasses
metody a zadejte pole všech reprezentací, které chcete (v upřednostňovaném pořadí). - 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 (INSCoding
INSPasteboardWriting
aINSPasteboardReading
). 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-C dokumentaci 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.