62

Why does the following code sometimes causes an Exception with the contents "CLIPBRD_E_CANT_OPEN":

Clipboard.SetText(str);

This usually occurs the first time the Clipboard is used in the application and not after that.

Roman Ananyev
  • 519
  • 1
  • 5
  • 16
Robert Wagner
  • 17,515
  • 9
  • 56
  • 72

9 Answers9

47

This is caused by a bug/feature in Terminal Services clipboard (and possible other things) and the .NET implementation of the clipboard. A delay in opening the clipboard causes the error, which usually passes within a few milliseconds.

The solution is to try multiple times within a loop and sleep in between.

for (int i = 0; i < 10; i++)
{
    try
    {
        Clipboard.SetText(str);
        return;
    }
    catch { }
    System.Threading.Thread.Sleep(10);
} 
Jason Plank
  • 2,336
  • 5
  • 31
  • 40
Robert Wagner
  • 17,515
  • 9
  • 56
  • 72
  • 4
    If you look at the internals of Clipboard.SetText, on .NET 2.0 SP1 at least, you'll see it already has a retry/wait loop. Retries up to 10 times with a 100ms delay. – Mike Dimmick Sep 26 '08 at 16:07
  • 17
    @Mike: System.Windows.Forms.Clipboard has a retry, but System.Windows.Clipboard from WPF doesn't. – Cameron MacFarland Sep 18 '09 at 00:46
  • 7
    This is insane, pure madness... :D I spent 2h w/o any reference trying to figure out how to make the bloody thing work and you tell me I should have just tried UNTIL it worked in a bloody loop? Madness! :) – bor Jan 17 '14 at 14:52
  • 13
    `catch {}` is a bad practice. Replace with `catch (COMException ex) { const uint CLIPBRD_E_CANT_OPEN = 0x800401D0; if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN) throw; }` – Maxence Oct 26 '17 at 12:31
  • Indeed WinForms' clipboard has a retry, visible [here](https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Clipboard.cs,171) – Borislav Ivanov Oct 24 '18 at 10:45
43

Actually, I think this is the fault of the Win32 API.

To set data in the clipboard, you have to open it first. Only one process can have the clipboard open at a time. So, when you check, if another process has the clipboard open for any reason, your attempt to open it will fail.

It just so happens that Terminal Services keeps track of the clipboard, and on older versions of Windows (pre-Vista), you have to open the clipboard to see what's inside... which ends up blocking you. The only solution is to wait until Terminal Services closes the clipboard and try again.

It's important to realize that this is not specific to Terminal Services, though: it can happen with anything. Working with the clipboard in Win32 is a giant race condition. But, since by design you're only supposed to muck around with the clipboard in response to user input, this usually doesn't present a problem.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Tadmas
  • 6,238
  • 3
  • 39
  • 31
29

I know this question is old, but the problem still exists. As mentioned before, this exception occurs when the system clipboard is blocked by another process. Unfortunately, there are many snipping tools, programs for screenshots and file copy tools which can block the Windows clipboard. So you will get the exception every time you try to use Clipboard.SetText(str) when such a tool is installed on your PC.

Solution:

never use

Clipboard.SetText(str);

use instead

Clipboard.SetDataObject(str);
pr0gg3r
  • 4,254
  • 1
  • 36
  • 27
13

I solved this issue for my own app using native Win32 functions: OpenClipboard(), CloseClipboard() and SetClipboardData().

Below the wrapper class I made. Could anyone please review it and tell if it is correct or not. Especially when the managed code is running as x64 app (I use Any CPU in the project options). What happens when I link to x86 libraries from x64 app?

Thank you!

Here's the code:

public static class ClipboardNative
{
    [DllImport("user32.dll")]
    private static extern bool OpenClipboard(IntPtr hWndNewOwner);

    [DllImport("user32.dll")]
    private static extern bool CloseClipboard();

    [DllImport("user32.dll")]
    private static extern bool SetClipboardData(uint uFormat, IntPtr data);

    private const uint CF_UNICODETEXT = 13;

    public static bool CopyTextToClipboard(string text)
    {
        if (!OpenClipboard(IntPtr.Zero)){
            return false;
        }

        var global = Marshal.StringToHGlobalUni(text);

        SetClipboardData(CF_UNICODETEXT, global);
        CloseClipboard();

        //-------------------------------------------
        // Not sure, but it looks like we do not need 
        // to free HGLOBAL because Clipboard is now 
        // responsible for the copied data. (?)
        //
        // Otherwise the second call will crash
        // the app with a Win32 exception 
        // inside OpenClipboard() function
        //-------------------------------------------
        // Marshal.FreeHGlobal(global);

        return true;
    }
}
Mar
  • 941
  • 1
  • 10
  • 18
  • 1
    PS: I also tried to use Managed `Clipboard.SetText()` call before calling the "native" function (i.e. used native way only if the managed one did not work). But if the managed version fails it locks the clipboard and after that native version also fails to open clipboard. – Mar May 11 '15 at 10:53
  • 2
    This answer works like a charm <3, thank you. I'm on .net framework 4.8 and nothing else seemed to help. – hakamairi Jul 06 '21 at 07:52
  • 1
    This should be the top answer, it's the most safe approach – Christoph B Aug 02 '21 at 10:51
  • Beware that this did not work in .net 6 (and possibly lower Versions) with WPF. You have to specify a valid window-HWND instead IntPtr.Zero to OpenClipboard, then it works. – MHolzmayr Jul 05 '23 at 09:24
  • @MHolzmayr, thank you, I'll check that and update the code above! My answer was for .NET Framework 4, I didn't switch to .NET yet. – Mar Jul 06 '23 at 11:31
  • This solution has a problem, although it can copy the text to the clipboard, but can not be pasted into the WPF TextBox or Notepad, which only supports plain text area, for VSCode, it can be pasted normally. – CodingNinja Aug 23 '23 at 09:22
10

Actually there could be another issue at hand. The framework call (both the WPF and winform flavors) to something like this (code is from reflector):

private static void SetDataInternal(string format, object data)
{
    bool flag;
    if (IsDataFormatAutoConvert(format))
    {
        flag = true;
    }
    else
    {
        flag = false;
    }
    IDataObject obj2 = new DataObject();
    obj2.SetData(format, data, flag);
    SetDataObject(obj2, true);
}

Note that SetDataObject is always called with true in this case.

Internally that triggers two calls to the win32 api, one to set the data and one to flush it from your app so it's available after the app closes.

I've seen several apps (some chrome plugin, and a download manager) that listen to the clipboard event. As soon as the first call hits, the app will open the clipboard to look into the data, and the second call to flush will fail.

Haven't found a good solution except to write my own clipboard class that uses direct win32 API or to call setDataObject directly with false for keeping data after the app closes.

Yishai Galatzer
  • 8,791
  • 2
  • 32
  • 41
2

Use the WinForms version (yes, there is no harm using WinForms in WPF applications), it handles everything you need:

System.Windows.Forms.Clipboard.SetDataObject(yourText, true, 10, 100);

This will attempt to copy yourText to the clipboard, it remains after your app exists, will attempt up to 10 times, and will wait 100ms between each attempt.

Ref. https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard.setdataobject?view=netframework-4.7.2#System_Windows_Forms_Clipboard_SetDataObject_System_Object_System_Boolean_System_Int32_System_Int32_

sarh
  • 6,371
  • 4
  • 25
  • 29
Bret Pehrson
  • 54
  • 1
  • 6
1

This happen to me in my WPF application. I got OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN)).

i use

ApplicationCommands.Copy.Execute(null, myDataGrid);

solution is to clear the clipboard first

Clipboard.Clear();
ApplicationCommands.Copy.Execute(null, myDataGrid);
Ellix4u
  • 566
  • 5
  • 9
  • If `Beyond Clipboard` is running - the `Clipboard.Clear()` will raise the exact same exception. tested with `Beyond Compare` version 4.2.3.22587 – itsho Apr 11 '18 at 06:52
1

That's not a solution, just some additional information on how to reproduce it when all solutions work on your PC and fail somewhere else. As mentioned in the accepted answer - clipboard can be busy by some other app. You just need to handle this failure properly, to explain user somehow why it does not work.

So, just create a new console app with few lines below and run it. And while it is running - test your primary app on how it is handles busy clipboard:

using System;
using System.Runtime.InteropServices;

namespace Clipboard
{
    class Program
    {
        [DllImport("user32.dll")]
        private static extern bool OpenClipboard(IntPtr hWndNewOwner);

        [DllImport("user32.dll")]
        private static extern bool CloseClipboard();

        static void Main(string[] args)
        {
            bool res = OpenClipboard(IntPtr.Zero);
            Console.Write(res);
            Console.Read();
            CloseClipboard();
        }
    }
}
sarh
  • 6,371
  • 4
  • 25
  • 29
0

The difference between Cliboard.SetText and Cliboard.SetDataObject in WPF is that the text is not copied to the clipboard, only the pointer. I checked the source code. If we call SetDataObject(data, true) Clipoard.Flush() will also be called. Thanks to this, text or data is available even after closing the application. I think Windows applications only call Flush() when they are shutting down. Thanks to this, it saves memory and at the same time gives access to data without an active application.

Copy to clipboard:

IDataObject CopyStringToClipboard(string s)
{
  var dataObject = new DataObject(s);
  Clipboard.SetDataObject(dataObject, false);
  return dataObject;
}

Code when app or window is closed:

try
{
  if ((clipboardData != null) && Clipboard.IsCurrent(clipboardData))
    Clipboard.Flush();
}
catch (COMException ex) {}

clipboardData is a window class field or static variable.

Marek J
  • 1
  • 2