为什么以下代码有时会导致内容为“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);
}
实际上,我认为这是Win32 API 的错。
要在剪贴板中设置数据,您必须先将其打开。一次只有一个进程可以打开剪贴板。因此,当您检查时,如果另一个进程出于任何原因打开了剪贴板,您打开它的尝试将会失败。
碰巧终端服务会跟踪剪贴板,而在旧版本的 Windows(Vista 之前)上,您必须打开剪贴板才能查看里面的内容……这最终会阻止您。唯一的解决方案是等到终端服务关闭剪贴板并重试。
重要的是要意识到这并不特定于终端服务:它可以发生在任何事情上。在 Win32 中使用剪贴板是一个巨大的竞争条件。但是,由于按照设计,您只应该使用剪贴板来响应用户输入,这通常不会出现问题。
我知道这个问题很老,但问题仍然存在。如前所述,当系统剪贴板被另一个进程阻塞时会发生此异常。不幸的是,有许多截图工具、截图程序和文件复制工具可以阻止 Windows 剪贴板。Clipboard.SetText(str)
因此,当您在 PC 上安装此类工具时,每次尝试使用时都会出现异常。
解决方案:
从不使用
Clipboard.SetText(str);
改为使用
Clipboard.SetDataObject(str);
实际上手头可能还有另一个问题。框架调用(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 调用。
在内部,这会触发对 win32 api 的两次调用,一次用于设置数据,一次用于从您的应用程序中刷新数据,以便在应用程序关闭后可用。
我见过几个监听剪贴板事件的应用程序(一些 chrome 插件和下载管理器)。一旦第一次调用命中,应用程序将打开剪贴板以查看数据,第二次调用 flush 将失败。
除了编写我自己的使用直接 win32 API 的剪贴板类或使用 false 直接调用 setDataObject 以在应用程序关闭后保留数据之外,还没有找到一个好的解决方案。
我使用本机 Win32 函数为我自己的应用程序解决了这个问题:OpenClipboard()、CloseClipboard() 和 SetClipboardData()。
在我制作的包装类下面。任何人都可以检查它并告诉它是否正确。特别是当托管代码作为 x64 应用程序运行时(我在项目选项中使用 Any 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;
}
}
使用 WinForms 版本(是的,在 WPF 应用程序中使用 WinForms 没有害处),它可以处理您需要的一切:
System.Windows.Forms.SetDataObject(yourText, true, 10, 100);
这将尝试将 yourText 复制到剪贴板,它会在您的应用程序存在后保留,最多尝试 10 次,每次尝试之间将等待 100 毫秒。
这发生在我的 WPF 应用程序中。我的 OpenClipboard 失败(来自 HRESULT 的异常:0x800401D0 (CLIPBRD_E_CANT_OPEN))。
我用
ApplicationCommands.Copy.Execute(null, myDataGrid);
解决方法是先清除剪贴板
Clipboard.Clear();
ApplicationCommands.Copy.Execute(null, myDataGrid);