Reputation: 6368
Over the course of the last couple of hours I have been tracking down a fairly specific bug with that occurs because another application has the clipboard open. Essentially as the clipboard is a shared resource (as per "Why does my shared clipboard not work?") and you attempt to execute
Clipboard.SetText(string)
or
Clipboard.Clear().
The following exception is thrown:
System.Runtime.InteropServices.ExternalException: Requested Clipboard operation did not succeed. at System.Windows.Forms.Clipboard.ThrowIfFailed(Int32 hr) at System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 retryTimes, Int32 retryDelay) at System.Windows.Forms.Clipboard.SetText(String text, TextDataFormat format) at System.Windows.Forms.Clipboard.SetText(String text)
My initial solution was to retry after a short pause, until I realised that Clipboard.SetDataObject has fields for the number of times and the length of the delay. .NET's default behaviour is to try 10 times with a 100 msec delay.
There is one final thing that has been noted by the end user. That is, despite the exception being thrown, the copy to clipboard operation still works. I haven't been able to find any further information about why this may be.
My current solution to the issue is just to silently ignore the exception... is this really the best way?
Upvotes: 55
Views: 48939
Reputation: 1
I've actually come up with my own solution and it seems to be working for me.
// This allows the clipboard to have something copied to it.
public static void ClipboardPaste(String pasteString)
{
// This clears the clipboard
Clipboard.Clear();
// This is a "Try" of the statement inside {}, if it fails it goes to "Catch"
// If it "Catches" an exception. Then the function will retry itself.
try
{
// This, per some suggestions I found is a half second second wait before another
// clipboard clear and before setting the clipboard
System.Threading.Thread.Sleep(500);
Clipboard.Clear();
// This is, instead of using SetText another method to put something into
// the clipboard, it includes a retry/fail set up with a delay
// It retries 5 times with 250 milliseconds (0.25 second) between each retry.
Clipboard.SetDataObject(pasteString, false, 5, 250);
}
catch (Exception)
{
ClipboardPaste(pasteString);
}
}
This is obviously C#, however these methods are exposed to all Visual Studios. I have obviously created a looping function, as well as attempted to force it into the clipboard with retries.
Essentially here's the flow. Let's say you want to place the word clipboard into the clipboard, anywhere in your code (assuming this function is defined).
Yes, this does have a flaw if you know your clipboard will always have an exception no matter what (infinite loop). However I have not run into an infinite loop with this method yet. The other flaw is that it can take a couple of seconds (essentially slowing down your applications) before it will work, while it's attempting it may freeze your application, once it succeeds the application will continue anyway.
Upvotes: -4
Reputation: 1
This is bit crappy... But it solved my problem.
Retry the clear() after a delay.
More information is in the blog post How to handle a blocked clipboard - Clipboard.Clear() error.
Upvotes: 0
Reputation: 60872
Another workaround would be to use Clipboard.SetDataObject
instead of Clipboard.SetText
.
According to this MSDN article this method has two parameters - retryTimes and retryDelay - that you can use like this:
System.Windows.Forms.Clipboard.SetDataObject(
"some text", // Text to store in clipboard
false, // Do not keep after our application exits
5, // Retry 5 times
200); // 200 ms delay between retries
Upvotes: 47
Reputation: 2313
As the clipboard is shared by all UI applications, you will run into this from time to time. Obviously, you don't want your application to crash if it failed to write to the clipboard, so gracefully handling ExternalException is reasonable. I would suggest presenting an error to the user if the SetObjectData call to write to the clipboard fails.
A suggestion would be to use (via P/Invoke) user32!GetOpenClipboardWindow
to see if another application has the clipboard open. It will return the HWND of the window which has the clipboard open, or IntPtr.Zero
if no application had it open. You could spin on the value until its IntPtr.Zero
for a specified amount of time.
Upvotes: 28
Reputation: 89
By making use of Jeff Roe's code (Jeff's Code)
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetOpenClipboardWindow();
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int GetWindowText(int hwnd, StringBuilder text, int count);
private void btnCopy_Click(object sender, EventArgs e)
{
try
{
Clipboard.Clear();
Clipboard.SetText(textBox1.Text);
}
catch (Exception ex)
{
string msg = ex.Message;
msg += Environment.NewLine;
msg += Environment.NewLine;
msg += "The problem:";
msg += Environment.NewLine;
msg += getOpenClipboardWindowText();
MessageBox.Show(msg);
}
}
private string getOpenClipboardWindowText()
{
IntPtr hwnd = GetOpenClipboardWindow();
StringBuilder sb = new StringBuilder(501);
GetWindowText(hwnd.ToInt32(), sb, 500);
return sb.ToString();
// example:
// skype_plugin_core_proxy_window: 02490E80
}
you are able to handle the error in a pretty handy way.
I have managed to reduce the frequency of the error by making use of System.Windows.Forms.Clipboard
Instead of System.Windows.Clipboard
.
I stress that this doesn't fix the problem but it has reduced the occurrence for my application.
Upvotes: 3
Reputation: 3214
I ran into this error today. I decided to handle it by telling the user about the potentially misbehaving application. To do so, you can do something like this:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetOpenClipboardWindow();
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int GetWindowText(int hwnd, StringBuilder text, int count);
private void btnCopy_Click(object sender, EventArgs e)
{
try
{
Clipboard.Clear();
Clipboard.SetText(textBox1.Text);
}
catch (Exception ex)
{
string msg = ex.Message;
msg += Environment.NewLine;
msg += Environment.NewLine;
msg += "The problem:";
msg += Environment.NewLine;
msg += getOpenClipboardWindowText();
MessageBox.Show(msg);
}
}
private string getOpenClipboardWindowText()
{
IntPtr hwnd = GetOpenClipboardWindow();
StringBuilder sb = new StringBuilder(501);
GetWindowText(hwnd.ToInt32(), sb, 500);
return sb.ToString();
// example:
// skype_plugin_core_proxy_window: 02490E80
}
For me, the problem window title was "skype_plugin_core_proxy_window". I searched for info on that, and was surprised that it yielded only one hit, and that was in Russian. So I'm adding this answer, both to give another hit for that string, and to provide further help to bring potentially-misbehaving apps to light.
Upvotes: 13
Reputation: 121
Doing a Clipboard.Clear()
before Clipboard.SetDataObject(pasteString, true)
seems to do the trick.
The earlier suggestion of setting retryTimes
and retryDelay
didn't work for me and in any case the defaults are retryTimes = 10
and retryDelay = 100ms
Upvotes: 5
Reputation: 19204
Just call this first:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr CloseClipboard();
I noticed that if you're in the middle of a paste operation (WM_PASTE message), including during the TextChanged event, the clipboard remains locked by the window (the TextBox) receiving the event. So if you just call that "CloseClipboard" method inside the event handler, then you can call the managed Clipboard.Clear and Clipboard.SetText methods without any issues or delays.
Upvotes: 7