In this article

Copy and paste in Xamarin.Mac

03/14/2017

31 minutes to read

Contributors

In this article

This article covers working with the pasteboard to provide copy and paste in a Xamarin.Mac application. It shows how to work with standard data types that can be shared between multiple apps and how to support custom data within a given app.

Overview

When working with C# and .NET in a Xamarin.Mac application, you have access to the same pasteboard (copy and paste) support that a developer working in Objective-C has.

In this article we will be covering the two main ways to use the pasteboard in a Xamarin.Mac app:

Standard Data Types - Since pasteboard operations are typically carried out between two unrelated apps, neither app knows the types of data that the other supports. To maximize the potential for sharing, the pasteboard can hold multiple representations of a given item (using a standard set of common data types), this allow the consuming app to pick the version that is best suited for its needs.

Custom Data - To support the copying and pasting of complex data within your Xamarin.Mac you can define a custom data type that will be handled by the pasteboard. For example, a vector drawing app that allows the user to copy and paste complex shapes that are composed of multiple data types and points.

In this article, we'll cover the basics of working with the pasteboard in a Xamarin.Mac application to support copy and paste operations. It is highly suggested that you work through the Hello, Mac article first, specifically the Introduction to Xcode and Interface Builder and Outlets and Actions sections, as it covers key concepts and techniques that we'll be using in this article.

Getting started with the pasteboard

The pasteboard presents a standardized mechanism for exchanging data within a given application or between applications. The typical use for a pasteboard in a Xamarin.Mac application is to handle copy and paste operations, however a number of other operations are also supported (such as Drag & Drop and Application Services).

To get you off the ground quickly, we are going to start with a simple, practical introduction to using pasteboards in a Xamarin.Mac app. Later, we'll provide an in-depth explanation of how the pasteboard works and the methods used.

For this example, we will be creating a simple document based application that manages a window containing an image view. The user will be able to copy and paste images between documents in the app and to or from other apps or multiple windows inside the same app.

Creating the Xamarin project

First, we are going to create a new document based Xamarin.Mac app that we will be adding copy and paste support for.

Do the following:

Start Visual Studio for Mac and click the New Project... link.

Select Mac > App > Cocoa App, then click the Next button:

Enter MacCopyPaste for the Project Name and keep everything else as default. Click Next:

Click the Create button:

Add an NSDocument

Next we will add custom NSDocument class that will act as the background storage for the application's user interface. It will contain a single Image View and know how to copy an image from the view into the default pasteboard and how to take an image from the default pasteboard and display it in the Image View.

Right-click on the Xamarin.Mac project in the Solution Pad and select Add > New File..:

Enter ImageDocument for the Name and click the New button. Edit the ImageDocument.cs class and make it look like the following:

With this document in place, we'll create the user interface for the Xamarin.Mac app.

Building the user interface

Double-click the Main.storyboard file to open it in Xcode. Next, add a toolbar and an image well and configure them as follows:

Add a copy and paste Image Toolbar Item to the left side of the toolbar. We'll be using those as shortcuts to copy and paste from the Edit menu. Next, add four Image Toolbar Items to the right side of the toolbar. We'll use these to populate the image well with some default images.

For more information on working with toolbars, please see our Toolbars documentation.

Next, let's expose the following outlets and actions for our toolbar items and the image well:

Enabling the user interface

With our user interface created in Xcode and our UI element exposed via outlets and actions, we need to add the code to enable the UI. Double-click the ImageWindow.cs file in the Solution Pad and make it look like the following:

Again, we get the current, topmost window and use its ImageDocument class instance to see if the required image data exists. Then we use the MenuWillHighlightItem method to enable or disable each item based on this state.

Edit the AppDelegate.cs file and make the DidFinishLaunching method look like the following:

Testing the app

With everything in place, we are ready to test the application. Build and run the app and the main interface is displayed:

If you open the Edit menu, note that Cut, Copy and Paste are disabled because there is no image in the image well or in the default pasteboard:

If you add an image to the image well and reopen the Edit menu, the items will now be enabled:

If you copy the image and select New from the file menu, you can paste that image into the new window:

In the following sections, we'll take a detailed look at working with the pasteboard in a Xamarin.Mac application.

About the pasteboard

In macOS (formerly known as OS X) the pasteboard (NSPasteboard) provide support for several server processes such as Copy & Paste, Drag & Drop and Application Services. In the following sections, we'll take a closer look at several key pasteboard concepts.

What is a pasteboard?

The NSPasteboard class provides a standardized mechanism for exchanging information between applications or within a given app. The main function of a pasteboard is for handling copy and paste operations:

When the user selects an item in an app and uses the Cut or Copy menu item, one or more representations of the selected item are placed on the pasteboard.

When the user uses the Paste menu item (within the same app or a different one), the version of the data that it can handle is copied from the pasteboard and added to the app.

When the user initiates a drag operation, the drag data is copied to the pasteboard. If the drag operation ends with a drop onto another app, that app copies the data from the pasteboard.

For translation services, the data to be translated is copied to the pasteboard by the requesting app. The application service, retrieves the data from pasteboard, does the translation, then pastes the data back on the pasteboard.

In their simplest form, pasteboards are used to move data inside a given app or between apps and therefor exist in a special global memory area outside of the app's process. While the concepts of the pasteboards are easily grasps, there are several more complex details that must be considered. These will be covered in detail below.

Named pasteboards

A pasteboard can be public or private and may be used for a variety of purposes within an application or between multiple apps. macOS provides several standard pasteboards, each with a specific, well-defined usage:

For most situations, you'll use one of the system defined pasteboards. But there might be situations that require you to create your own pasteboards. In these situations, you can use the FromName (string name) method of the NSPasteboard class to create a custom pasteboard with the given name.

Optionally, you can call the CreateWithUniqueName method of the NSPasteboard class to create a uniquely named pasteboard.

Pasteboard items

Each piece of data that an application writes to a pasteboard is considered a Pasteboard Item and a pasteboard can hold multiple items at the same time. In this way, an app can write multiple versions of the data being copied to a pasteboard (for example, plain text and formatted text) and the retrieving app can read off only the data that it can process (such as the plain text only).

Data representations and uniform type identifiers

Pasteboard operations typically take place between two (or more) applications that have no knowledge of each other or the types of data that each can handle. As stated in the section above, to maximize the potential for sharing information, a pasteboard can hold multiple representations of the data being copied and pasted.

Each representation is identified via a Uniform Type Identifier (UTI), which is nothing more than a simple string that uniquely identifies the type of date being presented (for more information, please see Apple's Uniform Type Identifiers Overview documentation).

If you are creating a custom data type (for example, a drawing object in a vector drawing app), you can create your own UTI to uniquely identify it in copy and paste operations.

When an app prepares to paste data copied from a pasteboard, it must find the representation that best fits its abilities (if any exists). Typically this will be the richest type available (for example formatted text for a word processing app), falling back to the simplest forms available as required (plain text for a simple text editor).

Promised data

Generally speaking, you should provide as many representations of the data being copied as possible to maximize sharing between apps. However, because of time or memory constraints, it might be impractical to actually write each data type into the pasteboard.

In this situation, you can place the first data representation on the pasteboard and the receiving app can request a different representation, that can be generated on-the-fly just before the paste operation.

When you place the initial item in the pasteboard, you will specify that one or more of the other representations available are provided by an object that conforms to the NSPasteboardItemDataProvider interface. These objects will provide the extra representations on demand, as requested by the receiving app.

Change count

Each pasteboard maintains a Change Count that increments each time a new owner is declared. An app can determine if the pasteboard's contents have changed since the last time it examined it by checking the value of the Change Count.

Use the ChangeCount and ClearContents methods of the NSPasteboard class to modify a given pasteboard's Change Count.

Copying data to a pasteboard

You perform a copy operation by first accessing a pasteboard, clearing any existing contents and writing as many representations of the data as are required to the pasteboard.

Typically, you'll just be writing to the general pasteboard, as we have done in the example above. Any object that you send to the WriteObjects method must conform to the INSPasteboardWriting interface. Several, built-in classes (such as NSString, NSImage, NSURL, NSColor, NSAttributedString, and NSPasteboardItem) automatically conform to this interface.

If you are writing a custom data class to the pasteboard it must conform to the INSPasteboardWriting interface or be wrapped in an instance of the NSPasteboardItem class (see the Custom Data Types section below).

Reading data from a pasteboard

As stated above, to maximize the potential for sharing data between apps, multiple representations of the copied data may be written to the pasteboard. It is up to the receiving app to select the richest version possible for its capabilities (if any exists).

Simple paste operation

You read data from the pasteboard using the ReadObjectsForClasses method. It will require two parameters:

An array of NSObject based class types that you want to read from the pasteboard. You should order this with the most desired data type first, with any remaining types in decreasing preference.

A dictionary containing additional constraints (such as limiting to specific URL content types) or an empty dictionary if no further constraints are required.

The method returns an array of items that meet the criteria that we passed in and therefore contains at most the same number of data types that are requested. it is also possible that none of the requested types are present and an empty array will be returned.

For example, the following code checks to see if an NSImage exists in the general pasteboard and displays it in an image well if it does:

Requesting multiple data types

Based on the type of Xamarin.Mac application being created, it may be able to handle multiple representations of the data being pasted. In this situation, there are two scenarios for retrieving data from the pasteboard:

Make a single call to the ReadObjectsForClasses method and providing an array of all of the representations that you desire (in the preferred order).

Make multiple calls to the ReadObjectsForClasses method asking for a different array of types each time.

See the Simple Paste Operation section above for more details on retrieving data from a pasteboard.

Checking for existing data types

There are times that you might want to check if a pasteboard contains a given data representation without actually reading the data from the pasteboard (such as enabling the Paste menu item only when valid data exists).

Call the CanReadObjectForClasses method of the pasteboard to see if it contains a given type.

For example, the following code determines if the general pasteboard contains a NSImage instance:

Reading urls from the pasteboard

Based on the function of a given Xamarin.Mac app, it may be required to read URLs from a pasteboard, but only if they meet a given set of criteria (such as pointing to files or URLs of a specific data type). In this situation, you can specify additional search criteria using the second parameter of the CanReadObjectForClasses or ReadObjectsForClasses methods.

Custom data types

There are times when you will need to save your own custom types to the pasteboard from a Xamarin.Mac app. For example, a vector drawing app that allows the user to copy and paste drawing objects.

In this situation, you'll need to design your data custom class so that it inherits from NSObject and it conforms to a few interfaces (INSCoding, INSPasteboardWriting and INSPasteboardReading). Optionally, you can use a NSPasteboardItem to encapsulate the data to be copied or pasted.

Both of these options will be covered in detail below.

Using a custom class

In this section we will be expanding on the simple example app that we created at the start of this document and adding a custom class to track information about the image that we are copying and pasting between windows.

Add a new class to the project and call it ImageInfo.cs. Edit the file and make it look like the following:

Inheritance and interfaces

Before a custom data class can be written to or read from a pasteboard, it must conform to the INSPastebaordWriting and INSPasteboardReading interfaces. In addition, it must inherit from NSObject and also conform to the INSCoding interface:

Each representation is identified via a Uniform Type Identifier (UTI), which is nothing more than a simple string that uniquely identifies the type of data being presented (for more information, please see Apple's Uniform Type Identifiers Overview documentation).

For our custom format, we are creating our own UTI: "com.xamarin.image-info" (note that is in reverse notation just like an App Identifier). Our class is also capable of writing a standard string to the pasteboard (public.text).

Next, we need to create the object in the requested format that actually gets written to the pasteboard:

For the public.text type, we are returning a simple, formatted NSString object. For the custom com.xamarin.image-info type, we are using a NSKeyedArchiver and the NSCoder interface to encode the custom data class to a key/value paired archive. We will need to implement the following method to actually handle the encoding:

Currently only the WritingPromised option is available and should be used when a given type is only promised and not actually written to the pasteboard. For more information, please see the Promised Data section above.

With these methods in place, the following code can be used to write our custom class to the pasteboard:

For the com.xamarin.image-info type, we are telling the pasteboard to decode the key/value pair that we created with the NSKeyedArchiver when writing the class to the pasteboard by calling the initWithCoder: constructor that we added to the class.

Finally, we need to add the following method to read the other UTI data representations from the pasteboard:

Using a NSPasteboardItem

There might be times when you need to write custom items to the pasteboard that do not warrant the creation of a custom class or you want to provide data in a common format, only as required. For these situations, you can use a NSPasteboardItem.

A NSPasteboardItem provides fine-grained control over the data that is written to the pasteboard and is designed for temporary access - it should be disposed of after it has been written to the pasteboard.

Writing data

To write your custom data to an NSPasteboardItem you'll need to provide a custom NSPasteboardItemDataProvider. Add a new class to the project and call it ImageInfoDataProvider.cs. Edit the file and make it look like the following:

As we did with the custom data class, we need to use the Register and Export directives to expose it to Objective-C. The class must inherit from NSPasteboardItemDataProvider and must implement the FinishedWithDataProvider and ProvideDataForType methods.

Use the ProvideDataForType method to provide the data that will be wrapped in the NSPasteboardItem as follows:

Summary

This article has taken a detailed look at working with the pasteboard in a Xamarin.Mac application to support copy and paste operations. First, it introduced a simple example to get you familiar with standard pasteboards operations. Next, it took a detailed look at the pasteboard and how to read and write data from it. Finally, it looked at using a custom data type to support the copying and pasting of complex data types within an app.