Skip to main content

...

....

C# WPF Tutorial - Using The Clipboard [Intermediate]


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

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV