So I just noticed that we 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
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");
As you might expect, if you want to check if there is any text on the
clipboard, you would call:
if(Clipboard.ContainsData(DataFormats.Text))
{
//Do Stuff
}
And finally, to get a copy of that text off of the clipboard, you would
call:
string myStr = Clipboard.GetData(DataFormats.Text) as string;
//Do Stuff
Now, for the common formats like Text, there are some simple wrapper
functions on the
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; }
}
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:
[Serializable]
public class MyInfo
{
public int MyNumber { get; set; }
public string MyString { get; set; }
}
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
SetData
:MyInfo info = new MyInfo();
info.MyNumber = 10;
info.MyString = "10";
Clipboard.SetData("MyInfo", info);
And pulling that data off the clipboard again is as simple as the
following:
MyInfo info = Clipboard.GetData("MyInfo") as MyInfo;
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:
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);
The output:
Info2: 10 10
Info3: 10 10
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
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");
}
}
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
Clear
function:Clipboard.Clear();
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:
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);
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
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);
That code prints out the names of all the available data formats on the
clipboard. If you are observant, you may have noticed that
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.
Comments
Post a Comment