15

I will do my best to explain in detail what I'm trying to achieve.

I'm using C# with IntPtr window handles to perform a CTRL-C copy operation on an external application from my own C# application. I had to do this because there was no way of accessing the text directly using GET_TEXT. I'm then using the text content of that copy within my application. The problem here is that I have now overwritten the clipboard.

What I would like to be able to do is:

  1. Backup the original contents of the clipboard which could have been set by any application other than my own.
  2. Then perform the copy and store the value into my application.
  3. Then restore the original contents of the clipboard so that the user still has access to his/her original clipboard data.

This is the code I have tried so far:

private void GetClipboardText()
{

    text = "";

    IDataObject backupClipboad = Clipboard.GetDataObject();

    KeyboardInput input = new KeyboardInput(this);
    input.Copy(dialogHandle); // Performs a CTRL-C (copy) operation

    IDataObject clipboard = Clipboard.GetDataObject(); 
    if (clipboard.GetDataPresent(DataFormats.Text))
    {
        // Retrieves the text from the clipboard
        text = clipboard.GetData(DataFormats.Text) as string;
    }

    if (backupClipboad != null) 
    {
        Clipboard.SetDataObject(backupClipboad, true); // throws exception
    }
}

I am using the System.Windows.Clipboard and not the System.Windows.Forms.Clipboard. The reason for this was that when I performed the CTRL-C, the Clipboard class from System.Windows.Forms did not return any data, but the system clipboard did.

I looked into some of the low level user32 calls like OpenClipboard, EmptyClipboard, and CloseClipboard hoping that they would help my do this but so far I keep getting COM exceptions when trying to restore.

I thought perhaps this had to do with the OpenClipboard parameter which is expecting an IntPtr window handle of the application which wants to take control of the clipboard. Since I mentioned that my application does not have a GUI this is a challenge. I wasn't sure what to pass here. Maybe someone can shed some light on that?

Am I using the Clipboard class incorrectly? Is there a clear way to obtain the IntPtr window handle of an application with no GUI? Does anyone know of a better way to backup and restore the system clipboard?

Ben Aaronson
  • 6,955
  • 2
  • 23
  • 38
gtaborga
  • 773
  • 3
  • 13
  • 18
  • I ran into the same error and found this post. However, I got this error only if I call Clipboard.GetDataObject().SetData(myData). It works fine if I call Clipboard.SetDataObject(myData). Any explanation why? My app is WPF based if it matters. – newman Jun 19 '13 at 20:25

2 Answers2

22

It's folly to try to do this. You cannot faithfully restore the clipboard to its prior state. There could be dozens of unrendered data formats present using "delayed rendering", and if you attempt to render them all, you'll cause the source app to run out of resources. It's like walking into a resturaunt and saying "give me one of everything".

Suppose that the user has selected 500 rows x 100 columns in Excel, and has copied that to the clipboard. Excel "advertises" that it can produce this data in about 25 different formats, including Bitmap. Once you paste it as a Bitmap, you force Excel to render it as a bitmap. That's 50000 cells, and would be a bitmap approx 10,000 x 15,000 pixels. And you expect the user to wait around while Excel coughs that up, along with 24 other formats? Not feasible.

Furthermore, you're going to be triggering WM_DrawClipboard events, which will impact other clipboard viewers.

Give up.

Chris Thornton
  • 15,620
  • 5
  • 37
  • 62
  • 8
    “Programs should not transfer data into our out of the clipboard without an explicit instruction from the user.” — Charles Petzold, Programming Windows 3.1, Microsoft Press, 1992 – Chris Thornton Apr 05 '10 at 17:28
  • That would be why the only time I've ever written Clipboard handling code was when implementing Cut, Copy, and Paste in a menu in a .NET application. – Powerlord Apr 05 '10 at 17:33
  • I never intended to render all of the data being backed up. The amount of data retrieved using the CTRL-C is about the length of a sentence in text. I was hoping for a way to put back the original contents of the clipboard after using the clipboard for my own needs. Is it even possible to restore the clipboard without getting a 'can't open clipboard' error in this scenario? – gtaborga Apr 06 '10 at 05:10
  • 1
    @gtaborga - you can avoid the "can't open clipboard" by handling the error, sleeping for a brief wait, and re-trying. 3 strikes and you're out, I'd say though. Anyway, I don't get your coment about "The amount of data retrieved using the CTRL-C is about the length of a sentence in text". If you're going to backup the ORIGINAL clipboard contents, then that's what you need to consider for the worst-case memory scenario. – Chris Thornton Apr 07 '10 at 03:36
  • 1
    The question is NOT how to store the rendered data. The OP just wants to restore the clipboard state. So if Excel "advertised" 25 formats, the OP does NOT want to store all of them. He just wants to store the "advertisement" and restore it after his short operation. Of course it could be possible that the state of the advertising application has changed during the OP's operation, and hence the restored "advertisement" might no longer be valid, but this is unlikely since the OP's operation takes place so quickly. – drwatsoncode Aug 02 '14 at 14:34
  • 2
    @ricovox, the "advertisement" will be invalid the instant that the clipboard is overwritten by the OP's app. Restoring handles to delayed rendering formats will not work. It really won't. – Chris Thornton Aug 04 '14 at 13:16
  • 1
    Great answer, if unfortunate. Delayed rendering was a terrible concept. It made clipboard snippets [no longer fully serializable](http://stackoverflow.com/questions/6262454/c-sharp-backing-up-and-restoring-clipboard#comment63657050_6312036) (killing a whole category of useful apps) and subtly misleads the user about honoring their copy request. How many times have you hit Ctrl+C thinking your content was safely stowed away, then closed the app (or had it crash) and found Ctrl+V mysteriously does nothing. The deferral of processing also adds unintuitive lag at the time of paste. – rkagerer Oct 02 '16 at 13:34
  • The Windows clipboard has not been fully serializable since Windows 3.0, perhaps longer. – Chris Thornton Oct 03 '16 at 14:04
  • And still, you have implemented ClipMate, which does ... exactly that? Or not? How did you solve it there? – Thomas Weller Sep 25 '20 at 08:27
  • ClipMate does not attempt to retrieve all formats, it selectively goes after certain universally useful formats (Text, HTML, Bitmap, etc.) , plus any that the user has opted to include, which usually involves trial-and-error. – Chris Thornton Oct 12 '20 at 13:17
6

You could save the content of the clipboard in a dictionary, and restore it afterwards :

public IDictionary<string, object> GetClipboardData()
{
    var dict = new Dictionary<string, object>();
    var dataObject = Clipboard.GetDataObject();
    foreach(var format in dataObject.GetFormats())
    {
        dict.Add(format, dataObject.GetData(format));
    }
    return dict;
}

public void SetClipboardData(IDictionary<string, object> dict)
{
    var dataObject = Clipboard.GetDataObject();
    foreach(var kvp in dict)
    {
        dataObject.SetData(kvp.Key, kvp.Value);
    }
}

...

var backup = GetClipboardData();
// Do something with the clipboard...
...
SetClipboardData(backup);
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • The problem I'm having is that any SetData or SetDataObject I perform gives me a COM exception because it failed to open the clipboard. I will try that method but underlying issue is the clipboard seems locked. – gtaborga Apr 05 '10 at 14:55
  • 1
    This is the exception I get on the first SetData call in that loop: "Cannot SetData on a frozen OLE data object" – gtaborga Apr 05 '10 at 15:10
  • Using Clipboard.SetData I received this exception which is the one I get whenever I try to do a Set: OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN)) – gtaborga Apr 05 '10 at 15:43
  • 3
    Try this code, and then copy a large selection in Excel. Oh wait, make sure you save all open documents. Better make a restore point first, in case you trash your system. Use Process Explorer from System Internals to measure the amount of RAM and CPU used by Excel as you do this, and be sure to END PROCESS on both Excel and your app before your system crashes. It will be worth the trip. – Chris Thornton Apr 07 '10 at 03:41