WPF Tutorial - Using The Clipboard |
So I just noticed that we here at SOTC have never talked about the clipboard before - and for such a common (and annoying) part of software, that is just horribly lax on our part. So here I am to rectify the situation - we will be taking a look today at how to use the clipboard in WPF.
Working with the clipboard can be quite painful sometimes in .NET applications, since all we get is is a very thin wrapper around the OLE clipboard calls (and so if anything goes wrong, nasty COM exceptions get thrown). It also doesn't help that since the clipboard is a shared resource among all applications on a computer, your application is not guaranteed to be able to use it at any given point in time. So today we are going to take a look at how to deal with those situations while adding data to and getting data from the clipboard. And don't worry, we will look at using our own custom formats for that data as well.
So first off, how do you get to the clipboard at all? Well, you use the Clipboard class, in the
Now, because the clipboard is again a shared resource, it can't know anything about .NET types (or any particular language's types for that matter). That means that the way to identify types of data on the clipboard are through format strings. There are a number of standard format strings that represent standard formats, and you can get to those through the DataFormats class. So, for instance, you wanted to put some text on the clipboard, you would call:
As you might expect, if you want to check if there is any text on the clipboard, you would call:
And finally, to get a copy of that text off of the clipboard, you would call:
Now, for the common formats like Text, there are some simple wrapper functions on the
Ok, but what about custom data? Well, it is actually really easy. For instance, say I wanted to store an instance of the following class on the clipboard for some reason:
The first thing that we need to do is mark it as Serializable, because that is how .NET places data on the clipboard (it serializes it). So now the class definition will look like so:
Now, we just need to choose a sufficiently unique format string - generally, I just go with the name of the class I'm putting on the clipboard, so in this case, "MyInfo". And now we can just call
And pulling that data off the clipboard again is as simple as the following:
Ok, now that we have all that out of the way, we can go into the nitty-gritty. So one useful thing to know is that whenever you call SetData, you are replacing the current contents of the clipboard with whatever you are setting. Another thing to note is that the call to SetData immediately copies whatever you are handing it, so you can feel free to destroy or modify your original afterward, and in addition every call to GetData will return a new copy of the data. For instance:
The output:
The other very important thing to know is that any one of these clipboard calls can fail without warning. The MSDN documentation does not state this, but they can all throw exceptions of type
Yes, I know that that is horrific and ugly and all sorts of bad things - but it is actually the recommended way to deal with the situation. So generally, in any application where I need to do a lot of clipboard activity, I will write a helper class that wraps the standard clipboard call, so that this try block doesn't have to be littered everywhere. In case you were wondering, the reason that thread sleep is there is to give whatever other application currently has the clipboard open some time to finish and close it. If we didn't give any time, it is almost guaranteed that the second call will fail too.
Ok, what else is there...ah yes, clear! Generally, there isn't any need to do this, but the clipboard class does give a way to clear the clipboard, the
Yeah, nothing really special there. And actually, all it is is a wrapper to the underlying OleSetClipboard where it is passing null as the DataObject.
And that brings us to the DataObject! What is the DataObject you ask? Well, it turns out that all these clipboard calls we have looked at so far are actually just manipulating a DataObject and then putting that object on the clipboard. It is actually the DataObject that is answering all those questions about what types of data the clipboard contains. The reason we care about the DataObject is because it allows us to put multiple formats of data on the clipboard at the same time. Take a look at the following:
In this example, we are placing two data formats in a DataObject, and then calling SetDataObject to place it on the clipboard. This way, we can get the specialized "MyInfo" data back out later, but other applications can get the text out if they want to.
There is also a corresponding
That code prints out the names of all the available data formats on the clipboard. If you are observant, you may have noticed that
Well, that about covers it for this crash course in using the clipboard in WPF. If there is anything in particular that was unclear or that you would like me to go into more detail one, let me know in the comments.
Working with the clipboard can be quite painful sometimes in .NET applications, since all we get is is a very thin wrapper around the OLE clipboard calls (and so if anything goes wrong, nasty COM exceptions get thrown). It also doesn't help that since the clipboard is a shared resource among all applications on a computer, your application is not guaranteed to be able to use it at any given point in time. So today we are going to take a look at how to deal with those situations while adding data to and getting data from the clipboard. And don't worry, we will look at using our own custom formats for that data as well.
So first off, how do you get to the clipboard at all? Well, you use the Clipboard class, in the
System.Windows
namespace. This static class has a number of methods on it for querying and interacting with the clipboard in a number of ways. However, the three important ones to start off with are SetData
, ContainsData
, and GetData
. In a general sense, they do exactly what you might expect - put some data on the clipboard, check if a certain type of data is on the clipboard, and get a certain type of data off of the clipboard.Now, because the clipboard is again a shared resource, it can't know anything about .NET types (or any particular language's types for that matter). That means that the way to identify types of data on the clipboard are through format strings. There are a number of standard format strings that represent standard formats, and you can get to those through the DataFormats class. So, for instance, you wanted to put some text on the clipboard, you would call:
Clipboard.SetData(DataFormats.Text, "A String");
if(Clipboard.ContainsData(DataFormats.Text))
{
//Do Stuff
}
{
//Do Stuff
}
string myStr = Clipboard.GetData(DataFormats.Text) as string;
//Do Stuff
//Do Stuff
Clipboard
class, like SetText
, ContainsText
, and GetText
. But all they do is call the corresponding data function with the correct data format.Ok, but what about custom data? Well, it is actually really easy. For instance, say I wanted to store an instance of the following class on the clipboard for some reason:
public class MyInfo
{
public int MyNumber { get; set; }
public string MyString { get; set; }
}
{
public int MyNumber { get; set; }
public string MyString { get; set; }
}
[Serializable]
public class MyInfo
{
public int MyNumber { get; set; }
public string MyString { get; set; }
}
public class MyInfo
{
public int MyNumber { get; set; }
public string MyString { get; set; }
}
SetData
:MyInfo info = new MyInfo();
info.MyNumber = 10;
info.MyString = "10";
Clipboard.SetData("MyInfo", info);
info.MyNumber = 10;
info.MyString = "10";
Clipboard.SetData("MyInfo", info);
MyInfo info = Clipboard.GetData("MyInfo") as MyInfo;
MyInfo info1 = new MyInfo();
info1.MyNumber = 10;
info1.MyString = "10";
Clipboard.SetData("MyInfo", info1);
info1.MyNumber = 12;
info1.MyString = "Q";
MyInfo info2 = Clipboard.GetData("MyInfo") as MyInfo;
Console.WriteLine("Info2: " + info2.MyNumber + " " + info2.MyString);
info2.MyNumber = 34;
info2.MyString = "A";
MyInfo info3 = Clipboard.GetData("MyInfo") as MyInfo;
Console.WriteLine("Info3: " + info3.MyNumber + " " + info3.MyString);
info1.MyNumber = 10;
info1.MyString = "10";
Clipboard.SetData("MyInfo", info1);
info1.MyNumber = 12;
info1.MyString = "Q";
MyInfo info2 = Clipboard.GetData("MyInfo") as MyInfo;
Console.WriteLine("Info2: " + info2.MyNumber + " " + info2.MyString);
info2.MyNumber = 34;
info2.MyString = "A";
MyInfo info3 = Clipboard.GetData("MyInfo") as MyInfo;
Console.WriteLine("Info3: " + info3.MyNumber + " " + info3.MyString);
Info2: 10 10
Info3: 10 10
Info3: 10 10
COMException
. The most common one that I've seen has the error "CLIPBRD_E_CANT_OPEN" which means that the application was unable to open the clipboard. This is generally because another application currently has the clipboard open. The sad thing about this is that generally the best thing to do is to just try again in a moment, because it will probably work. So for example, the 'correct' version of the SetData call above looks something like this:MyInfo info1 = new MyInfo();
info1.MyNumber = 10;
info1.MyString = "10";
try
{
Clipboard.SetData("MyInfo", info1);
}
catch(System.Runtime.InteropServices.COMException)
{
System.Threading.Thread.Sleep(0);
try
{
Clipboard.SetData("MyInfo", info1);
}
catch(System.Runtime.InteropServices.COMException)
{
MessageBox.Show("Can't Access Clipboard");
}
}
info1.MyNumber = 10;
info1.MyString = "10";
try
{
Clipboard.SetData("MyInfo", info1);
}
catch(System.Runtime.InteropServices.COMException)
{
System.Threading.Thread.Sleep(0);
try
{
Clipboard.SetData("MyInfo", info1);
}
catch(System.Runtime.InteropServices.COMException)
{
MessageBox.Show("Can't Access Clipboard");
}
}
Ok, what else is there...ah yes, clear! Generally, there isn't any need to do this, but the clipboard class does give a way to clear the clipboard, the
Clear
function:Clipboard.Clear();
And that brings us to the DataObject! What is the DataObject you ask? Well, it turns out that all these clipboard calls we have looked at so far are actually just manipulating a DataObject and then putting that object on the clipboard. It is actually the DataObject that is answering all those questions about what types of data the clipboard contains. The reason we care about the DataObject is because it allows us to put multiple formats of data on the clipboard at the same time. Take a look at the following:
MyInfo info1 = new MyInfo();
info1.MyNumber = 10;
info1.MyString = "10";
DataObject data = new DataObject();
data.SetData("MyInfo", info1);
data.SetData(DataFormats.Text, "Info: "
+ info1.MyNumber + " " + info1.MyString);
Clipboard.SetDataObject(data);
info1.MyNumber = 10;
info1.MyString = "10";
DataObject data = new DataObject();
data.SetData("MyInfo", info1);
data.SetData(DataFormats.Text, "Info: "
+ info1.MyNumber + " " + info1.MyString);
Clipboard.SetDataObject(data);
There is also a corresponding
GetDataObject
call on Clipboard
, which, instead of returning a particular format of data, will return the entire DataObject on the clipboard. This allows you to do things like the following:IDataObject data = Clipboard.GetDataObject();
string[] formats = data.GetFormats();
foreach (string format in formats)
Console.WriteLine(format);
string[] formats = data.GetFormats();
foreach (string format in formats)
Console.WriteLine(format);
GetDataObject
actually returns an IDataObject
, not a DataObject
. This interface allows you to write your own special kinds of DataObjects, in case you don't like some of the behaviors of the standard .NET one. But that is stuff for an advanced tutorial!Well, that about covers it for this crash course in using the clipboard in WPF. If there is anything in particular that was unclear or that you would like me to go into more detail one, let me know in the comments.