以下代码为什么会有时出现“CLIPBRD_E_CANT_OPEN”异常:
Clipboard.SetText(str);
这通常发生在应用程序第一次使用剪贴板时,而不是之后。
这是由终端服务剪贴板(以及可能的其他因素)和剪贴板的.NET实现中的一个错误/特性引起的。打开剪贴板的延迟会导致错误,通常在几毫秒内就会消失。
解决方法是在循环内尝试多次,并在它们之间睡眠。
for (int i = 0; i < 10; i++)
{
try
{
Clipboard.SetText(str);
return;
}
catch { }
System.Threading.Thread.Sleep(10);
}
catch {}
是一种不好的做法。应该用 catch (COMException ex) { const uint CLIPBRD_E_CANT_OPEN = 0x800401D0; if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN) throw; }
替代。 - Maxence实际上,我认为这是Win32 API的错误。
要设置剪贴板中的数据,您必须先打开它。一次只能有一个进程打开剪贴板。因此,当您检查时,如果另一个进程出于任何原因打开了剪贴板,则您尝试打开它将失败。
恰好终端服务跟踪剪贴板,在旧版Windows(Vista之前)上,您必须打开剪贴板才能查看其中的内容...这最终会阻止您。唯一的解决方案是等待终端服务关闭剪贴板,然后再尝试。
重要的是要意识到,这不仅限于终端服务:任何情况都可能发生。在Win32中使用剪贴板是一个巨大的竞争条件。但是,由于设计上您只应该响应用户输入而混淆剪贴板,因此通常不会出现问题。
我知道这个问题很旧,但问题仍然存在。如前所述,在系统剪贴板被其他进程阻塞时会出现此异常。不幸的是,有许多截屏工具、截图程序和文件复制工具可以阻止Windows剪贴板。因此,每当你在电脑上安装了这样的工具并尝试使用Clipboard.SetText(str)
时,都会出现此异常。
解决方法:
永远不要使用
Clipboard.SetText(str);
使用替代方案
Clipboard.SetDataObject(str);
Clipboard.SetDataObject(str, true);
来使剪贴板数据在应用程序外可访问。 - deadlydog我使用本地的Win32函数解决了自己应用程序的这个问题:OpenClipboard(),CloseClipboard()和SetClipboardData()。
下面是我制作的包装类。能否请有人检查一下并告诉我它是否正确。特别是当托管代码作为x64应用程序运行时(我在项目选项中使用任何CPU)。如果从x64应用程序链接到x86库会发生什么?
谢谢!
这是代码:
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;
}
}
Clipboard.SetText()
调用(即仅在托管版本无效时使用本地方式)。但是如果托管版本失败,它会锁定剪贴板,此后本地版本也无法打开剪贴板。 - Mar实际上可能还存在另一个问题。框架调用(包括 WPF 和 Winform 版本)类似于这样的东西(代码来自于反编译):
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);
}
请注意,在这种情况下,SetDataObject总是以true为参数调用。那不是解决方案,只是一些额外的信息,可以在所有解决方案都在您的PC上工作但在其他地方失败时如何重现它。如采纳的答案所述 - 剪贴板可能被其他应用程序占用。您只需要正确处理此故障,以说明用户为什么无法正常工作。
因此,只需使用下面的几行创建一个新的控制台应用程序并运行它。而在它运行时-测试您的主要应用程序,看它如何处理繁忙的剪贴板:
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();
}
}
}
使用WinForms版本(在WPF应用程序中使用WinForms没有任何问题),它可以处理您所需的一切:
System.Windows.Forms.Clipboard.SetDataObject(yourText, true, 10, 100);
ApplicationCommands.Copy.Execute(null, myDataGrid);
Clipboard.Clear();
ApplicationCommands.Copy.Execute(null, myDataGrid);
Clipboard.Clear()
将引发完全相同的异常。已使用“Beyond Compare”版本4.2.3.22587进行测试。 - itshoIDataObject CopyStringToClipboard(string s)
{
var dataObject = new DataObject(s);
Clipboard.SetDataObject(dataObject, false);
return dataObject;
}
应用程序或窗口关闭时的代码:
try
{
if ((clipboardData != null) && Clipboard.IsCurrent(clipboardData))
Clipboard.Flush();
}
catch (COMException ex) {}
clipboardData是一个窗口类的字段或静态变量。