【问题标题】:CLIPBRD_E_CANT_OPEN error when setting the Clipboard from .NET从 .NET 设置剪贴板时出现 CLIPBRD_E_CANT_OPEN 错误
【发布时间】:2008-09-16 01:50:35
【问题描述】:

为什么以下代码有时会导致内容为“CLIPBRD_E_CANT_OPEN”的异常:

Clipboard.SetText(str);

这通常发生在应用程序中第一次使用剪贴板时,而不是之后。

【问题讨论】:

  • 这是一个相当笨拙的解决方案 - 这真的是唯一的方法吗?
  • 这看起来是 MS 在 Forms 中实现它的方式。这个问题是关于 WPF 的(虽然我没有意识到这很重要)。

标签: .net wpf clipboard


【解决方案1】:

这是由终端服务剪贴板中的错误/功能(以及可能的其他事情)和剪贴板的 .NET 实现引起的。打开剪贴板的延迟会导致错误,该错误通常会在几毫秒内过去。

解决方案是在一个循环中尝试多次,然后在其间休眠。

for (int i = 0; i < 10; i++)
{
    try
    {
        Clipboard.SetText(str);
        return;
    }
    catch { }
    System.Threading.Thread.Sleep(10);
} 

【讨论】:

  • 如果您至少在 .NET 2.0 SP1 上查看 Clipboard.SetText 的内部结构,您会发现它已经有一个重试/等待循环。最多重试 10 次,延迟 100 毫秒。
  • @Mike:System.Windows.Forms.Clipboard 有重试,但 WPF 中的 System.Windows.Clipboard 没有。
  • 这太疯狂了,纯粹是疯狂...... :D 我花了 2 小时没有任何参考资料试图弄清楚如何让这该死的东西起作用,你告诉我我应该尝试直到它起作用一个血腥的循环?疯狂! :)
  • catch {} 是一种不好的做法。替换为catch (COMException ex) { const uint CLIPBRD_E_CANT_OPEN = 0x800401D0; if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN) throw; }
  • 确实WinForms的剪贴板有重试,可见here
【解决方案2】:

其实我觉得是fault of the Win32 API

要在剪贴板中设置数据,您必须先open it。一次只有一个进程可以打开剪贴板。因此,当您检查时,如果另一个进程出于任何原因打开了剪贴板,那么您打开它的尝试将会失败。

碰巧终端服务会跟踪剪贴板,而在旧版本的 Windows(Vista 之前)上,您必须打开剪贴板才能查看里面的内容......这最终会阻止您。唯一的解决方案是等到终端服务关闭剪贴板后再试。

重要的是要意识到这并非特定于终端服务:它可能发生在任何事情上。在 Win32 中使用剪贴板是一个巨大的竞争条件。但是,由于根据设计,您只应该使用剪贴板来响应用户输入,因此这通常不会出现问题。

【讨论】:

    【解决方案3】:

    我知道这个问题很老了,但问题仍然存在。如前所述,当系统剪贴板被另一个进程阻塞时会发生此异常。不幸的是,有许多截图工具、截图程序和文件复制工具可以阻止 Windows 剪贴板。因此,当您在 PC 上安装此类工具时,每次尝试使用 Clipboard.SetText(str) 时都会出现异常。

    解决方案:

    从不使用

    Clipboard.SetText(str);
    

    改用

    Clipboard.SetDataObject(str);
    

    【讨论】:

    • @K_Rol:看起来 Yishai Galatzer 的回答解释了这一点。
    • 我得到“'String'类型的值不能转换为'DataObject'”?
    • 仅供参考,我必须使用 Clipboard.SetDataObject(str, true); 才能在应用程序外部访问剪贴板数据。
    【解决方案4】:

    实际上手头可能还有另一个问题。框架调用(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 插件和下载管理器)。一旦第一次调用命中,应用程序将打开剪贴板以查看数据,第二次调用刷新将失败。

    除了编写我自己的使用直接win32 API的剪贴板类或直接使用false调用setDataObject以在应用关闭后保留数据之外,还没有找到好的解决方案。

    【讨论】:

      【解决方案5】:

      我使用本机 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;
          }
      }
      

      【讨论】:

      • PS:我还尝试在调用“本机”函数之前使用托管 Clipboard.SetText() 调用(即,仅当托管函数不起作用时才使用本机方式)。但是,如果托管版本失败,它会锁定剪贴板,之后本机版本也无法打开剪贴板。
      • 这个答案就像一个魅力
      • 这应该是最佳答案,这是最安全的方法
      【解决方案6】:

      使用 WinForms 版本(是的,在 WPF 应用程序中使用 WinForms 并没有什么坏处),它可以处理您需要的一切:

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

      这将尝试将 yourText 复制到剪贴板,它会在您的应用存在后保留,最多尝试 10 次,每次尝试之间将等待 100 毫秒。

      参考。 https://docs.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_

      【讨论】:

        【解决方案7】:

        这发生在我的 WPF 应用程序中。我的 OpenClipboard 失败(来自 HRESULT 的异常:0x800401D0 (CLIPBRD_E_CANT_OPEN))。

        我用

        ApplicationCommands.Copy.Execute(null, myDataGrid);
        

        解决办法是先清除剪贴板

        Clipboard.Clear();
        ApplicationCommands.Copy.Execute(null, myDataGrid);
        

        【讨论】:

        • 如果 Beyond Clipboard 正在运行 - Clipboard.Clear() 将引发完全相同的异常。使用Beyond Compare 4.2.3.22587 版测试
        猜你喜欢
        • 2013-10-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-26
        • 1970-01-01
        • 2012-11-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多