Копирование и вставка в Xamarin.Mac

В этой статье рассматривается работа с вставкой для предоставления копирования и вставки в приложение Xamarin.Mac. В нем показано, как работать со стандартными типами данных, которые можно совместно использовать между несколькими приложениями и как поддерживать пользовательские данные в данном приложении.

Обзор

При работе с C# и .NET в приложении Xamarin.Mac у вас есть доступ к той же доске вставки (копирования и вставки), в которую работает Objective-C разработчик.

В этой статье мы рассмотрим два основных способа использования вставки в приложении Xamarin.Mac:

  1. Стандартные типы данных. Так как операции вставки обычно выполняются между двумя несвязанными приложениями, ни один из приложений не знает типы данных, поддерживаемых другими. Чтобы максимально увеличить потенциал совместного использования, доска может содержать несколько представлений заданного элемента (с использованием стандартного набора общих типов данных), что позволяет потребляющим приложением выбирать версию, которая лучше всего подходит для своих потребностей.
  2. Пользовательские данные . Для поддержки копирования и вставки сложных данных в Xamarin.Mac можно определить настраиваемый тип данных, который будет обрабатываться вставкой. Например, векторное приложение для рисования, позволяющее пользователю копировать и вставлять сложные фигуры, состоящие из нескольких типов данных и точек.

Example of the running app

В этой статье мы рассмотрим основы работы с вставкой в приложении Xamarin.Mac для поддержки операций копирования и вставки. Настоятельно рекомендуется сначала ознакомиться со статьей Hello, Mac , в частности в разделах "Введение в Xcode" и "Конструктор интерфейсов" и "Торговых точек" и "Действия ", поскольку рассматриваются основные понятия и методы, которые мы будем использовать в этой статье.

Возможно, вам потребуется ознакомиться с классами И методами C# вObjective-Cразделе документа Xamarin.Mac Internals, а также объяснить RegisterExport и атрибуты, используемые для подключения классов C# к Objective-C объектам и элементам пользовательского интерфейса.

Начало работы с вставкой

В вставке представлен стандартизированный механизм обмена данными в определенном приложении или между приложениями. Обычное использование вставки в приложении Xamarin.Mac заключается в обработке операций копирования и вставки, однако также поддерживаются ряд других операций (таких как перетаскивание и службы приложений).

Чтобы быстро вывести вас из земли, мы начнем с простого, практического введение в использование вставок в приложении Xamarin.Mac. Позже мы предоставим подробное описание работы вставки и используемых методов.

В этом примере мы создадим простое приложение на основе документа, которое управляет окном, содержащим представление изображения. Пользователь сможет копировать и вставлять изображения между документами в приложении и из других приложений или нескольких окон в одном приложении.

Создание проекта Xamarin

Во-первых, мы создадим новое приложение Xamarin.Mac на основе документа, для которых мы добавим поддержку копирования и вставки.

Выполните следующие действия.

  1. Запустите Visual Studio для Mac и щелкните ссылку "Создать проект...".

  2. Выберите приложение Mac App>Cocoa, а затем нажмите кнопку "Далее":>

    Creating a new Cocoa app project

  3. Введите MacCopyPaste имя проекта и сохраните все остальные значения по умолчанию. Нажмите Далее.

    Setting the name of the project

  4. Нажмите кнопку "Создать":

    Confirming the new project settings

Добавление NSDocument

Далее мы добавим пользовательский NSDocument класс, который будет выступать в качестве фонового хранилища для пользовательского интерфейса приложения. Он будет содержать одно представление изображения и знать, как скопировать изображение из представления в доску вставки по умолчанию и как взять изображение из доски вставки по умолчанию и отобразить его в представлении изображения.

Щелкните правой кнопкой мыши проект Xamarin.Mac на панели решения и выберите "Добавить>новый файл"..:

Adding an NSDocument to the project

Введите в поле Имя значение ImageDocument и нажмите кнопку Новый. Измените класс ImageDocument.cs и сделайте его следующим образом:

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

Давайте рассмотрим некоторый код ниже.

Следующий код предоставляет свойство для проверки наличия данных изображения на доске вставки по умолчанию, если изображение доступно, true возвращается еще 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);
    }
}

Следующий код копирует изображение из подключенного представления изображений в доску вставки по умолчанию:

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

}

И следующий код вставляет изображение из доски вставки по умолчанию и отображает его в подключенном представлении изображения (если доска вставки содержит допустимое изображение):

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

В этом документе мы создадим пользовательский интерфейс для приложения Xamarin.Mac.

Создание пользовательского интерфейса

Дважды щелкните файл Main.storyboard , чтобы открыть его в Xcode. Затем добавьте панель инструментов и изображение и настройте их следующим образом:

Editing the toolbar

Добавьте копию и вставьте элемент панели инструментов изображения слева от панели инструментов. Мы будем использовать эти сочетания клавиш для копирования и вставки из меню "Изменить". Затем добавьте четыре элемента панели инструментов изображения в правой части панели инструментов. Мы будем использовать их для заполнения изображения хорошо некоторыми изображениями по умолчанию.

Дополнительные сведения о работе с панелями инструментов см. в документации по панелям инструментов.

Далее давайте представим следующие точки и действия для элементов панели инструментов и изображения:

Creating outlets and actions

Дополнительные сведения о работе с точками и действиями см. в разделе " Точки и действия " нашей документации Hello, Mac .

Включение пользовательского интерфейса

Благодаря нашему пользовательскому интерфейсу, созданному в Xcode, и нашему элементу пользовательского интерфейса, предоставляемому через точки и действия, необходимо добавить код для включения пользовательского интерфейса. Дважды щелкните файл ImageWindow.cs на панели решения и сделайте его следующим образом:

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

Давайте рассмотрим этот код подробно ниже.

Во-первых, мы предоставляем экземпляр класса, созданного ImageDocument выше:

private ImageDocument _document;
...

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

С помощью ExportWillChangeValue и DidChangeValue, мы настроили Document свойство, чтобы разрешить кодирование ключей и привязку данных в Xcode.

Мы также предоставляем изображение из образа, который мы добавили в наш пользовательский интерфейс в Xcode со следующим свойством:

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

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

При загрузке и отображении главного окна мы создадим экземпляр класса ImageDocument и вложим к нему изображение пользовательского интерфейса со следующим кодом:

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

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

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

Наконец, в ответ на запрос пользователя, щелкнув элементы панели инструментов копирования и вставки, мы вызываем экземпляр класса для выполнения фактической ImageDocument работы:

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

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

Включение меню "Файл и изменение"

Последнее, что нам нужно сделать, — включить новый пункт меню из меню "Файл" (создать новые экземпляры главного окна) и включить элементы меню "Вырезать", "Копировать" и "Вставить" в меню "Изменить".

Чтобы включить новый пункт меню, измените файл AppDelegate.cs и добавьте следующий код:

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

Дополнительные сведения см. в разделе "Работа с несколькими windows " документации по Windows .

Чтобы включить элементы меню "Вырезать", "Копировать" и "Вставить", измените файл AppDelegate.cs и добавьте следующий код:

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

Для каждого пункта меню мы получаем текущее, самое верхнее окно и приведение его к нашему ImageWindow классу:

var window = NSApplication.SharedApplication.KeyWindow as ImageWindow;

Оттуда мы вызываем ImageDocument экземпляр класса этого окна для обработки действий копирования и вставки. Например:

window.Document.CopyImage (sender);

Мы хотим , чтобы элементы меню "Вырезать", "Копировать " и "Вставить " будут доступны только в том случае, если данные изображения находятся на вставке по умолчанию или на рисунке текущего активного окна.

Давайте добавим файл EditMenuDelegate.cs в проект Xamarin.Mac и сделайте его следующим образом:

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

Опять же, мы получаем текущее, самое верхнее окно и используем его ImageDocument экземпляр класса, чтобы узнать, существуют ли необходимые данные изображения. Затем мы используем MenuWillHighlightItem метод для включения или отключения каждого элемента на основе этого состояния.

Измените файл AppDelegate.cs и сделайте DidFinishLaunching метод следующим образом:

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

Во-первых, мы отключаем автоматическое включение и отключение элементов меню в меню "Изменить". Затем мы присоединяем экземпляр класса, созданного EditMenuDelegate выше.

Дополнительные сведения см. в документации по меню.

Проверка приложения

Все готово к тестированию приложения. Отображается сборка и запуск приложения и основной интерфейс:

Running the application

Если открыть меню "Изменить", обратите внимание, что вырезать, копировать и вставить отключены, так как в изображении нет изображения хорошо или в доске вставки по умолчанию:

Opening the Edit menu

Если вы добавите изображение в изображение и снова откройте меню "Изменить", элементы теперь будут включены:

Showing the Edit menu items are enabled

Если скопировать изображение и выбрать "Создать" в меню "Файл", его можно вставить в новое окно:

Pasting an image into a new window

В следующих разделах мы подробно рассмотрим работу с вставкой в приложении Xamarin.Mac.

Сведения о вставке

В macOS (прежнее название — OS X) вставка (NSPasteboard) обеспечивает поддержку нескольких серверных процессов, таких как копирование и вставка, перетаскивание и службы приложений. В следующих разделах мы рассмотрим несколько концепций вставки ключей.

Что такое вставка?

Класс NSPasteboard предоставляет стандартизированный механизм обмена информацией между приложениями или в определенном приложении. Основная функция вставки — обработка операций копирования и вставки:

  1. Когда пользователь выбирает элемент в приложении и использует пункт меню "Вырезать " или "Копировать ", на доске помещаются одно или несколько представлений выбранного элемента.
  2. Когда пользователь использует элемент меню "Вставка " (в одном приложении или другом), версия данных, которую она может обрабатывать, копируется из вставки и добавляется в приложение.

Менее очевидное использование вставки включает операции поиска, перетаскивания, перетаскивания и служб приложений:

  • Когда пользователь инициирует операцию перетаскивания, данные перетаскивания копируются в вставку. Если операция перетаскивания заканчивается падением на другое приложение, это приложение копирует данные из вставки.
  • Для служб перевода данные, которые необходимо преобразовать, копируются в вставку с помощью запрашивающего приложения. Служба приложений извлекает данные из вставки, выполняет перевод, а затем вставляет данные обратно на вставку.

В самой простой форме вставки используются для перемещения данных внутри заданного приложения или между приложениями и существуют в специальной глобальной области памяти за пределами процесса приложения. Хотя понятия вставки легко понять, существует несколько более сложных деталей, которые необходимо рассмотреть. Эти сведения подробно описаны ниже.

Именованные вставки

Вставка может быть общедоступной или частной и может использоваться для различных целей в приложении или между несколькими приложениями. macOS предоставляет несколько стандартных досок вставки, каждый из которых имеет определенное, четко определенное использование:

  • NSGeneralPboard— По умолчанию для операций вырезания, копирования и вставки по умолчанию.
  • NSRulerPboard — поддерживает операции вырезания, копирования и вставки на линейках.
  • NSFontPboard — поддерживает операции вырезания, копирования и вставки объектов NSFont .
  • NSFindPboard — Поддерживает панели поиска, относящиеся к приложению, которые могут совместно использовать текст поиска.
  • NSDragPboard — поддерживает операции перетаскивания .

В большинстве случаев вы будете использовать одну из системных вставок. Но могут возникнуть ситуации, требующие создания собственных досок вставки. В таких ситуациях можно использовать FromName (string name) метод NSPasteboard класса для создания настраиваемой вставки с заданным именем.

При необходимости можно вызвать CreateWithUniqueName метод NSPasteboard класса, чтобы создать уникально именованную доску вставки.

Вставка элементов

Каждый фрагмент данных, записываемый приложением в вставку, считается элементом вставки, а доска вставки может хранить несколько элементов одновременно. Таким образом, приложение может записывать несколько версий данных, копируемых в вставку (например, обычный текст и форматированный текст), а приложение извлечения может считывать только те данные, которые он может обрабатывать (например, только обычный текст).

Представления данных и универсальные идентификаторы типов

Операции вставки обычно выполняются между двумя (или более) приложениями, у которых нет знаний друг о другом или типах данных, которые могут обрабатываться каждой из них. Как указано в приведенном выше разделе, чтобы максимально повысить потенциал совместного доступа к информации, доска может содержать несколько представлений копируемых и вставленных данных.

Каждое представление определяется с помощью универсального идентификатора типа (UTI), которая не является более простой строкой, которая однозначно определяет тип представленной даты (дополнительные сведения см. в документации по универсальным идентификаторам типов Apple).

Если вы создаете пользовательский тип данных (например, объект рисования в векторном приложении рисования), можно создать собственный UTI для уникальной идентификации его в операциях копирования и вставки.

Когда приложение готовится вставить данные, скопированные из вставки, оно должно найти представление, которое лучше всего соответствует его возможностям (если таковой существует). Обычно это будет самый богатый тип (например, форматированный текст для приложения обработки слов), возвращающийся к самым простым формам, доступным по мере необходимости (обычный текст для простого текстового редактора).

Обещанные данные

Как правило, необходимо предоставить максимальное количество представлений копируемых данных, чтобы максимально повысить общий доступ между приложениями. Однако из-за ограничений времени или памяти может оказаться непрактичным для записи каждого типа данных в вставку.

В этой ситуации можно поместить первое представление данных в вставку, а принимающее приложение может запросить другое представление, которое можно создать непосредственно перед операцией вставки.

При размещении начального элемента в вставке необходимо указать, что одно или несколько других представлений, доступных объектом, соответствующим интерфейсу NSPasteboardItemDataProvider . Эти объекты предоставляют дополнительные представления по запросу, как запрашивается принимающее приложение.

Количество изменений

Каждая вставка поддерживает число изменений, которое увеличивается при каждом объявлении нового владельца. Приложение может определить, изменилось ли содержимое вставки с момента последнего просмотра, проверка значение счетчика изменений.

ChangeCountClearContents Используйте методы NSPasteboard класса для изменения количества изменений в заданной вставке.

Копирование данных в вставку

Чтобы выполнить операцию копирования, сначала перейдите к доске вставки, очищая любое существующее содержимое и записывая столько данных, сколько требуется для вставки.

Например:

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

Как правило, вы просто напишите в общую доску вставки, как мы сделали в приведенном выше примере. Любой объект, отправляемый методу WriteObjects, должен соответствовать интерфейсу INSPasteboardWriting . Несколько встроенных классов (напримерNSStringNSImage, , NSURL, NSColor, NSAttributedStringи NSPasteboardItem) автоматически соответствуют этому интерфейсу.

Если вы пишете пользовательский класс данных в вставку, он должен соответствовать INSPasteboardWriting интерфейсу или быть упакован в экземпляр NSPasteboardItem класса (см . раздел "Пользовательские типы данных" ниже).

Чтение данных из вставки

Как уже упоминалось выше, чтобы максимально повысить потенциал совместного использования данных между приложениями, в вставку можно записать несколько представлений скопированных данных. До получения приложения можно выбрать самую богатую версию для ее возможностей (если она существует).

Простая операция вставки

Данные из вставки считываются с помощью ReadObjectsForClasses метода. Для этого потребуется два параметра:

  1. Массив NSObject типов классов на основе, которые необходимо прочитать из вставки. Для этого сначала следует упорядочить наиболее нужный тип данных с любыми остальными типами при снижении предпочтения.
  2. Словарь, содержащий дополнительные ограничения (например, ограничение на определенные типы содержимого URL-адресов) или пустой словарь, если дополнительные ограничения не требуются.

Метод возвращает массив элементов, которые соответствуют критериям, которые мы передали, и поэтому содержит не более одного количества запрошенных типов данных. Также возможно, что ни один из запрошенных типов не присутствует, а пустой массив будет возвращен.

Например, следующий код проверка, чтобы узнать, существует ли в NSImage общей вставке и отображает его в изображении хорошо, если это делает:

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

Запрос нескольких типов данных

В зависимости от типа создаваемого приложения Xamarin.Mac он может обрабатывать несколько представлений вставляемых данных. В этой ситуации существует два сценария получения данных из вставки:

  1. Выполните один вызов ReadObjectsForClasses метода и укажите массив всех требуемых представлений (в предпочтительном порядке).
  2. Выполнение нескольких вызовов ReadObjectsForClasses метода, запрашивающего разные массивы типов каждый раз.

Дополнительные сведения о получении данных из вставки см. в разделе "Простая операция вставки" выше.

Проверка существующих типов данных

Иногда может потребоваться проверка, если в вставке содержится заданное представление данных без фактического чтения данных из вставки (например, включение элемента меню "Вставка" только в том случае, если допустимые данные существуют).

CanReadObjectForClasses Вызовите метод вставки, чтобы узнать, содержит ли он заданный тип.

Например, следующий код определяет, содержит ли общий NSImage буфер вставки экземпляр:

public bool ImageAvailableOnPasteboard {
    get {
        // Initialize the pasteboard
        NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
        Class [] classArray  = { new Class ("NSImage") };

        // Check to see if an image is on the pasteboard
        return pasteboard.CanReadObjectForClasses (classArray, null);
    }
}

Чтение URL-адресов из вставки

На основе функции данного приложения Xamarin.Mac может потребоваться чтение URL-адресов из вставки, но только в том случае, если они соответствуют заданному набору критериев (например, указывая на файлы или URL-адреса определенного типа данных). В этой ситуации можно указать дополнительные критерии поиска с помощью второго параметра CanReadObjectForClasses или ReadObjectsForClasses методов.

Пользовательские типы данных

Существует время, когда вам потребуется сохранить собственные пользовательские типы в вставку из приложения Xamarin.Mac. Например, векторное приложение рисования, позволяющее пользователю копировать и вставлять объекты рисования.

В этой ситуации необходимо разработать пользовательский класс данных, чтобы он наследовался и NSObject соответствует нескольким интерфейсам (INSCodingINSPasteboardWritingиINSPasteboardReading). При необходимости можно использовать инкапсулировать NSPasteboardItem данные для копирования или вставки.

Оба этих варианта подробно описаны ниже.

Использование пользовательского класса

В этом разделе мы добавим простое приложение, созданное в начале этого документа, и добавим пользовательский класс для отслеживания сведений о изображении, которое мы копируем и вставляем между окнами.

Добавьте новый класс в проект и вызовите его ImageInfo.cs. Измените файл и сделайте его следующим образом:

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

В следующих разделах мы подробно рассмотрим этот класс.

Наследование и интерфейсы

Прежде чем пользовательский класс данных может быть записан в или считываться из вставки, он должен соответствовать INSPastebaordWriting интерфейсам и INSPasteboardReading интерфейсам. Кроме того, он должен наследоваться и NSObject соответствовать интерфейсу INSCoding :

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

Класс также должен быть предоставлен Objective-C с помощью Register директивы, и он должен предоставлять все необходимые свойства или методы с помощью Export. Например:

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

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

Мы предоставляем два поля данных, которые будет содержать этот класс: имя изображения и его тип (jpg, png и т. д.).

Дополнительные сведения см. в разделе документации по Xamarin.Mac Internals для классов И методов Objective-C C#, которые ExportRegister используются для подключения классов C# к Objective-C объектам и элементам пользовательского интерфейса.

Конструкторы

Для нашего пользовательского класса данных потребуется два конструктора (должным образом предоставляемые Objective-Cим данные), чтобы его можно было считывать из вставки:

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

Во-первых, мы предоставляем пустой конструктор под методом initпо умолчаниюObjective-C.

Затем мы предоставляем соответствующий NSCoding конструктор, который будет использоваться для создания нового экземпляра объекта из вставки при вставке под экспортируемым именем initWithCoder.

Этот конструктор принимает NSCoder (как создается NSKeyedArchiver при записи в вставку), извлекает парные данные ключа и значения и сохраняет его в поля свойств класса данных.

Запись в вставку

В соответствии с INSPasteboardWriting интерфейсом необходимо предоставить два метода и при необходимости третий метод, чтобы класс можно было записать в вставку.

Во-первых, нам нужно сообщить в вставке, в каком типе данных можно записать настраиваемый класс:

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

Каждое представление определяется с помощью универсального идентификатора типа (UTI), которая не является более простой строкой, которая однозначно идентифицирует тип представленных данных (дополнительные сведения см. в документации по универсальным идентификаторам типов Apple).

Для нашего пользовательского формата мы создаем собственный UTI: com.xamarin.image-info (обратите внимание, что в обратном нотации точно так же, как идентификатор приложения). Наш класс также может писать стандартную строку в вставку (public.text).

Затем необходимо создать объект в запрошенном формате, который фактически записывается в доску вставки:

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

public.text Для типа мы возвращаем простой форматированный NSString объект. Для пользовательского com.xamarin.image-info типа мы используем NSKeyedArchiver интерфейс для NSCoder кодирования пользовательского класса данных в парном архиве ключа и значения. Для обработки кодирования необходимо реализовать следующий метод:

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

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

Отдельные пары "ключ-значение" записываются в кодировщик и будут декодированы с помощью второго конструктора, который мы добавили выше.

При необходимости можно включить следующий метод, чтобы определить любые параметры при записи данных в вставку:

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

В настоящее время доступен только WritingPromised этот параметр, и его следует использовать, если данный тип обещается только и не записывается в доску вставки. Дополнительные сведения см. в разделе "Обещанные данные " выше.

С помощью этих методов можно использовать следующий код для записи пользовательского класса в вставку:

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

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

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

Чтение из вставки

В соответствии с INSPasteboardReading интерфейсом необходимо предоставить три метода, чтобы пользовательский класс данных можно было считывать из вставки.

Во-первых, нам нужно сообщить, какой тип данных представляет, что пользовательский класс может считывать из буфера обмена:

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

Опять же, они определяются как простые UTIs и являются теми же типами, которые мы определили в разделе записи в разделе "Вставка " выше.

Затем необходимо указать, как каждый из типов UTI будет считываться с помощью следующего метода:

[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 Для типа мы сообщаем вставке декодировать пару "ключ-значение", созданную при NSKeyedArchiver написании класса в вставку, вызвав initWithCoder: конструктор, добавленный в класс.

Наконец, необходимо добавить следующий метод для чтения других представлений данных UTI из вставки:

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

Используя все эти методы, пользовательский класс данных можно считывать из вставки с помощью следующего кода:

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

Использование NSPasteboardItem

Иногда может возникнуть необходимость записи пользовательских элементов в вставку, которая не гарантирует создание пользовательского класса или предоставление данных в обычном формате только по мере необходимости. В таких ситуациях можно использовать NSPasteboardItem.

Предоставляет NSPasteboardItem подробный контроль над данными, которые записываются в вставку и предназначены для временного доступа. Он должен быть удален после записи в вставку.

Запись данных

Чтобы записать пользовательские данные в NSPasteboardItem настраиваемый объект, вам потребуется предоставить пользовательский NSPasteboardItemDataProviderкод. Добавьте новый класс в проект и вызовите его ImageInfoDataProvider.cs. Измените файл и сделайте его следующим образом:

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

Как и в пользовательском классе данных, необходимо использовать Register и Export директивы для его предоставления Objective-C. Класс должен наследоваться от NSPasteboardItemDataProvider и должен реализовывать FinishedWithDataProvider методы и ProvideDataForType методы.

ProvideDataForType Используйте метод для предоставления данных, которые будут упакованы в NSPasteboardItem следующие данные:

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

}

В этом случае мы храним две части информации о нашем изображении (Name и ImageType) и записываем их в простую строку (public.text).

Введите данные в вставку, используйте следующий код:

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

Чтение данных

Чтобы считывать данные обратно из вставки, используйте следующий код:

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

Итоги

В этой статье подробно рассматривается работа с вставкой в приложении Xamarin.Mac для поддержки операций копирования и вставки. Во-первых, он представил простой пример, чтобы ознакомиться с стандартными операциями вставки. Затем он подробно посмотрел на вставку и как считывать и записывать данные из него. Наконец, он рассмотрел использование пользовательского типа данных для поддержки копирования и вставки сложных типов данных в приложении.