C# and the Clipboard

IronPython & Windows Forms, Part VIII

Windows Forms

Note

This is part of a series of tutorials on using IronPython with Windows Forms.

 

 

Introduction

Welcome to another exciting episode in the IronPython & Windows Forms tutorial series. This entry is going to be a bit different from previous ones. There will be no GUI, so no screenshots. Sorry about that. Sad

Despite the depressing news, this should still be worth following. We're going to look at creating a custom C# class and access it from IronPython [1]. The excuse I've found for this, is putting data on the clipboard. Because we are working with the clipboard, we will still be using the System.Windows.Forms assembly.

With even a moderately complex application you are likely to need to support ordinary cut and paste operations that we are all so used to. Copying what seems to the user like relatively simple data (say a list of items with attributes) may mean that storing (and then restoring) data using a text format may be very inconvenient. What you want to do is put your own data-structures onto the clipboard. You may also want to put text on the clipboard so that your users can paste an approximation of their data into other applications.

Luckily .NET provides a (relatively) easy way of placing text and data onto the clipboard.

In order to store objects on the clipboard we will use the System.Windows.Forms.Clipboard object, which has convenient methods for storing objects and text (and also retrieving them again later).

To ensure that we can tell if an object of our type is available on the clipboard, we are going to use a DataObject that implements the IDataObject Interface, with our own custom DataFormat.

Don't worry if this sounds complicated, we'll go over it step-by-step and should end up with some re-usable code at the end of it.

So Why the C# ?

There is a problem however, and this where the C# comes in. In order to store objects on the clipboard they must be serializable. Unfortunately .NET doesn't know how to serialize arbitrary IronPython objects.

In order to enable this, a class must be marked as Serializable. Serializable is an Attribute (effectively a kind of class decorator), and guess what; we can't access these from IronPython.

This means that we need to create a custom container class in C# to store our objects with. As we won't be able to store our own objects directly, we'll use the pickle module to serialize our Python objects.

Luckily C#, despite being a gnarly static language, replete with curly-braces and type declarations, is quite a nice language. As well as being faster than Python, and compiling to executables [2], it has some features that Python lacks like native arrays and enumerations.

Note

You may think that performance is the main reason you might want to drop into C# when working with IronPython.

At Resolver Systems we have used C# for the clipboard and to handle access to unmanaged code, but never had to use it for performance reasons. We have always managed to optimise our Python code whenever we have found performance to be an issue.

In order to create our class, we'll use Visual Studio Express 2005 for C# which is available for free download for Windows [3].

Once you've (finally) completed the download and install select new project from the file menu.

Choose Class Library from the list of options and enter ClipboardDataContainer as the project name, then click on OK.

You should end up with a window that looks a bit like this (although the code will be different) :

Visual Studio

If you are working on Mono, you can use Monodevelop, which will look like this [4] :

Mono Develop

There, and I said no screenshots. Smile

This creates a new project, which will result in a new assembly (class library) instead of an executable.

The code we need is :

using System;

namespace ClipboardDataContainer
{
    [Serializable()]
    public class ClipboardDataContainer
    {
        public string data;

        public ClipboardDataContainer(string data)
        {
            this.data = data;
        }
    }
}

I'm afraid this isn't the place for a tutorial on C#, but it should have give you a very basic taste for it.

using System; (note the semi-colon line terminator) is the equivalent of Python's from System import *, except that it isn't frowned upon. Wink

We need system for the Serializable class, which you can see set as an attribute of the ClipboardDataContainer which is declared inside a namespace.

Namespaces can be spread across more than one file, and are used to group classes together. This is very much like the abstract idea of a namespace in Python.

The class is public (meaning we can use it from outside the namespace). Notice that the lines which precede curly braces don't have the semi-colon terminator.

Inside the class we declare a public attribute data, which we declare to be of the string type.

Following this is the method ClipboardDataContainer. Because it has the same name as the class it is a constructor for the class. You can define multiple constructors in a class, so long as they have different argument signatures.

Our constructor takes a single argument, data, which is co-incidentally a string.

Inside the constructor we set the data attribute (field in C# terminology) to the data argument we were passed in. this is the C# implicit version of self in Python.

This is all very simple, but it will do the job nicely for us. Smile

Now would be a good time to save the solution. The default location for it will be somewhere in your My Documents folder.

If you hit F6 (or select build solution from the Build menu), then Visual Studio will build your project (use F8 on Monodevelop). If all goes well (if it doesn't then you've done something wrong), then you will end up with a file called ClipboardDataContainer.dll [5] in the ClipboardDataContainer/bin/Release sub-directory of the ClipboardDataContainer project directory.

Copy this to the same directory as IronPython.dll. If you can't be bothered to do all this, you can download a prebuilt one from here.

Putting Data on the Clipboard

So now we have our container type, we can put it to use.

First we want to put a data structure, and potentially text as well, onto the clipboard. We can use any object that pickle can serialize, which includes class instances.

Just so that we do things in the right order, first I'll show you the import code we will need for the whole of this example.

import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('ClipboardDataContainer')

import sys

sys.path.append(r'C:\Python24\Lib')

import pickle
from ClipboardDataContainer import ClipboardDataContainer
from System.Windows.Forms import Clipboard, DataFormats, DataObject

Because we are using modules from the Python standard library we need to ensure the standard library is in sys.path. Note also how easy accessing our C# class is; one clr.AddReference and a single import.

The next snippet shows pickling a simple dictionary and initialising a ClipboardDataContainer instance with the resulting string :

dataStructure = {'one': 1, 'two': 'two', 'three': [1, 2, 'three']}
pickledData = pickle.dumps(dataStructure)
clipboardData  = ClipboardDataContainer(pickledData)

As I hurriedly mentioned at the start of this entry we need to initialise a data object (which implements the IDataObject interface) with our freshly baked ClipboardDataContainer instance.

So that we can conveniently retrieve this data later, we are going to define and specify that it is in our own custom data format. To do this we use the DataFormat class. It has a GetFormat method especially for the job.

Having created a new format object, we pass DataObject its Name [6] as well as the object we want to store.

dataFormat = DataFormats.GetFormat('Custom Format')
clipboardDataObject = DataObject(dataFormat.Name, clipboardData)
clipboardDataObject.SetText('Some text as well.')

Because the DataObject is a versatile object, you can use it to store multiple data-types simultaneously. For our purposes we want to store some text as well, so we use the SetText method.

For a list of other data-types (like Bitmap and Html) you could also put into the DataObject, look at the DataFormats Members.

The final part of the magic incantation, is to use the Clipboard object to actually put our object onto the clipboard :

persist = True
Clipboard.SetDataObject(clipboardDataObject, persist)

As you can see, we use the aptly named SetDataObject method of Clipboard.

The persist argument can be omitted, but if you set it to True then your data will remain on the clipboard even after your application has exited.

That's really all there is to putting data onto the clipboard. Smile

If you only wanted to store text, you could bypass a lot of the mumbo-jumbo, and just call Clipboard.SetText.

Fetching the Data Back

If you have placed data on the clipboard, then at some point you are going to want to fetch it back again (which reminds me, we need to look at events soon).

Retrieving data is even easier than storing it. First we have to check if there is any data of our 'type' on the clipboard. For this we will re-use the data format we defined earlier.

First, we pull a data object off the clipboard using the opposite of the way we stored it; Clipboard.GetDataObject.

It returns an instance of our good old friend DataObject. We can ask this if it has any of our data on it :

dataObject = Clipboard.GetDataObject()
if not dataObject.GetDataPresent(dataFormat.Name):
    print 'No data here...'
    sys.exit(1)

If our data is there, then we must ask the data object for it, and unpickle the data attribute (well, field seeing as this will be our .NET class) :

clipboardObject = dataObject.GetData(dataFormat.Name)
dataStructure = pickle.loads(clipboardObject.data)

On the other hand, if all we wanted to do was fetch the text from the clipboard, we could simply do :

text = Clipboard.GetText()

Putting it all Together

The next piece of code puts together everything we've used in this entry.

It defines three functions :

  • SetClipboard - puts data (and optionally text) on the clipboard
  • CheckDataOnClipboard - checks if there is data on the clipboard (returns True or False)
  • GetClipboard - fetches the data

It then shows an example of putting a simple data structure onto the clipboard along with some text, then fetching it back again and comparing with the original.

If you run it, open a text editor and paste: the text will still be on the clipboard :

import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('ClipboardDataContainer')

import sys
import pickle
from ClipboardDataContainer import ClipboardDataContainer
from System.Windows.Forms import Clipboard, DataFormats, DataObject

dataFormat = DataFormats.GetFormat('Custom Format')

def SetClipboard(dataObject, text=None, persist=True):
    pickledData = pickle.dumps(dataObject)
    clipboardData  = ClipboardDataContainer(pickledData)
    clipboardDataObject = DataObject(dataFormat.Name, clipboardData)
    if text is not None:
        clipboardDataObject.SetText(text)
    Clipboard.SetDataObject(clipboardDataObject, persist)

def CheckDataOnClipboard():
    dataObject = Clipboard.GetDataObject()
    return dataObject.GetDataPresent(dataFormat.Name)

def GetClipboard():
    # Must call 'CheckDataOnClipboard' first
    dataObject = Clipboard.GetDataObject()
    clipboardObject = dataObject.GetData(dataFormat.Name)
    return pickle.loads(clipboardObject.data)

dataStructure = {'one': 1, 'two': 'two', 'three': [1, 2, 'three']}

persist = True
SetClipboard(dataStructure, 'Hello World', persist)

if not CheckDataOnClipboard():
    print 'Data absent... oh dear :-('
    sys.exit(1)
print Clipboard.GetText()

newDataStructure = GetClipboard()
assert newDataStructure == dataStructure
print newDataStructure

So have fun and experiment. See what happens when you set persist to False (the text shouldn't remain on the clipboard).

Alternatively, keep persist as True and put data onto the clipboard from one process and retrieve it from another. Very Happy

Postscript

Hmmm... as it happens the CLR string is of course serializable. This means that you can just put the pickle string on the clipboard without needing a C# class.

Hopefully through this article you have seen how easy it is to extend IronPython with C#. You can easily extend this simple example to create your own custom datastructures which you can put on the clipboard without needing to use pickle.

[1]And you'll see that extending IronPython with C# is much easier than extending CPython with C.
[2]Which admittedly are still dependent on the .NET framework runtime.
[3]The full Visual Studio also works of course.
[4]Mono screenshot thanks to Mark Rees, using MonoDevelop 0.11 & Mono 1.1.16.1.
[5]This of course is the windows filename, on other platforms it will have a different file extension.
[6]Which I think is just a string.

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Fri Nov 27 18:32:35 2009.

Counter...