为什么以下代码有时会导致内容为" CLIPBRD_E_CANT_OPEN"的异常:
这通常是在应用程序中第一次使用剪贴板时发生,而不是在此之后发生。
这是由终端服务剪贴板中的错误/功能(以及可能的其他情况)和剪贴板的.NET实现引起的。打开剪贴板的延迟会导致错误,该错误通常会在几毫秒内消失。
解决方案是在一个循环中尝试多次,然后在两者之间睡眠。
1 2 3 4 5 6 7 8 9 10
| for (int i = 0; i < 10; i++)
{
try
{
Clipboard.SetText(str);
return;
}
catch { }
System.Threading.Thread.Sleep(10);
} |
实际上,我认为这是Win32 API的错误。
要在剪贴板中设置数据,您必须先打开它。一次只能打开一个进程剪贴板。因此,当您检查时,如果另一个进程由于某种原因打开了剪贴板,则您尝试打开它的尝试将失败。
碰巧的是,终端服务跟踪剪贴板,而在Windows的较早版本(Vista之前的版本)上,您必须打开剪贴板才能查看其中的内容...最终将您拒之门外。唯一的解决方案是等待终端服务关闭剪贴板,然后重试。
重要的是要意识到,这并非特定于终端服务:它可以发生在任何事情上。在Win32中使用剪贴板是一个巨大的竞争条件。但是,由于设计使您只能响应用户输入而随意修改剪贴板,因此通常不会出现问题。
我知道这个问题很旧,但是问题仍然存在。如前所述,当系统剪贴板被另一个进程阻止时,会发生此异常。不幸的是,有很多截图工具,用于截图的程序和文件复制工具可能会阻塞Windows剪贴板。因此,当您在PC上安装了这样的工具时,每次尝试使用Clipboard.SetText(str)时都会获得异常。
解:
永不使用
用代替
1
| Clipboard.SetDataObject(str); |
实际上,可能还有另一个问题。框架调用(WPF和winform风格)都类似这样(代码来自反射器):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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);
} |
请注意,在这种情况下,总是使用true调用SetDataObject。
在内部触发了两次Win32 API调用,一次调用设置数据,另一次从应用程序刷新数据,以便在应用程序关闭后可用。
我看过几个监听剪贴板事件的应用程序(一些chrome插件和一个下载管理器)。第一次调用时,应用程序将打开剪贴板以查看数据,而第二次刷新操作将失败。
除了编写自己的使用直接Win32 API的剪贴板类或使用false直接调用setDataObject来保存应用程序关闭后的数据外,没有找到一个好的解决方案。
我使用本地Win32函数为自己的应用程序解决了此问题:OpenClipboard(),CloseClipboard()和SetClipboardData()。
在我编写的包装器类下面。任何人都可以查看它,并告诉它是否正确。特别是当托管代码作为x64应用程序运行时(我在项目选项中使用Any CPU)。当我从x64应用程序链接到x86库时会发生什么?
谢谢!
这是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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;
}
} |
这在我的WPF应用程序中发生。我的OpenClipboard失败(HRESULT的异常:0x800401D0(CLIPBRD_E_CANT_OPEN))。
我用
1
| ApplicationCommands.Copy.Execute(null, myDataGrid); |
解决方法是先清除剪贴板
1 2
| Clipboard.Clear();
ApplicationCommands.Copy.Execute(null, myDataGrid); |
使用WinForms版本(是的,在WPF应用程序中使用WinForms没有危害),它可以处理您需要的所有内容:
1
| System.Windows.Forms.SetDataObject(yourText, true, 10, 100); |
这将尝试将yourText复制到剪贴板,在您的应用存在后仍保留,最多尝试10次,每次尝试之间等待100毫秒。
参考https://docs.microsoft.com/zh-cn/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_