【问题标题】:How to handle a blocked clipboard and other oddities如何处理被阻止的剪贴板和其他奇怪的东西
【发布时间】:2010-10-30 03:15:25
【问题描述】:

在过去的几个小时里,我一直在追踪一个相当具体的错误,因为另一个应用程序打开了剪贴板。本质上,剪贴板是共享资源(根据"Why does my shared clipboard not work?"),您尝试执行

Clipboard.SetText(string)

Clipboard.Clear().

抛出以下异常:

System.Runtime.InteropServices.ExternalException:请求的剪贴板操作未成功。 在 System.Windows.Forms.Clipboard.ThrowIfFailed(Int32 小时) 在 System.Windows.Forms.Clipboard.SetDataObject(对象数据,布尔副本,Int32 retryTimes,Int32 retryDelay) 在 System.Windows.Forms.Clipboard.SetText(字符串文本,TextDataFormat 格式) 在 System.Windows.Forms.Clipboard.SetText(字符串文本)

我最初的解决方案是在短暂的暂停后重试,直到我意识到 Clipboard.SetDataObject 具有用于次数和延迟长度的字段。 .NET 的默认行为是尝试 10 次,延迟为 100 毫秒。

最终用户注意到了最后一件事。也就是说,尽管抛出异常,复制到剪贴板操作仍然有效。我无法找到任何进一步的信息来说明为什么会这样。

我目前对该问题的解决方案只是默默地忽略异常......这真的是最好的方法吗?

【问题讨论】:

    标签: c# .net clipboard


    【解决方案1】:

    另一种解决方法是使用Clipboard.SetDataObject 而不是Clipboard.SetText

    根据this MSDN article,此方法有两个参数 - retryTimesretryDelay - 您可以这样使用:

    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
    

    【讨论】:

    • 我面临的问题是,即使 retryTimes 和 retryDelay (20/500) 的数字都很高,打开 Firefox 仍然会导致失败。好奇怪……
    • 请不要在.NET 框架中有两个Clipboard 类。一个在 System.Windows.Forms 命名空间中,另一个在 System.Windows 命名空间中(用于 WPF)。只有第一个具有重试次数和延迟的重载。
    【解决方案2】:

    由于剪贴板由所有 UI 应用程序共享,您会不时遇到这种情况。显然,您不希望您的应用程序在无法写入剪贴板时崩溃,因此优雅地处理 ExternalException 是合理的。如果写入剪贴板的 SetObjectData 调用失败,我建议向用户显示错误。

    建议使用(通过P/Invokeuser32!GetOpenClipboardWindow 来查看另一个应用程序是否打开了剪贴板。它将返回剪贴板打开的窗口的 HWND,如果没有应用程序打开它,则返回 IntPtr.Zero。您可以在指定的时间内旋转该值直到其IntPtr.Zero

    【讨论】:

    • 我已经阅读了有关 GetOpenClipboardWindow 的信息,这似乎是解决剪贴板访问问题的最佳解决方案。感谢您的回复。
    • 如何让进程阻塞剪贴板 - 请参阅:stackoverflow.com/questions/6583642/…
    • 先关闭剪贴板即可。看我的回答。
    【解决方案3】:

    我今天遇到了这个错误。我决定通过告诉用户潜在的行为不端的应用程序来处理它。为此,您可以执行以下操作:

    [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
    }
    

    对我来说,问题窗口的标题是“skype_plugin_core_proxy_window”。我搜索了这方面的信息,很惊讶它只命中了一个,而且是俄语的。因此,我添加了这个答案,既是为了对该字符串再次进行打击,也是为了提供进一步的帮助,以揭示可能存在行为不当的应用程序。

    【讨论】:

    • 在其他类似的主题中提到,有一些 Skype 版本在(共享)剪贴板方面表现非常糟糕
    【解决方案4】:

    先调用这个:

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    static extern IntPtr CloseClipboard();
    

    我注意到,如果您正在进行粘贴操作(WM_PASTE 消息),包括在 TextChanged 事件期间,剪贴板仍被接收事件的窗口(TextBox)锁定。因此,如果您只是在事件处理程序中调用“CloseClipboard”方法,那么您可以调用托管的 Clipboard.Clear 和 Clipboard.SetText 方法而不会出现任何问题或延迟。

    【讨论】:

      【解决方案5】:

      Clipboard.SetDataObject(pasteString, true) 之前执行Clipboard.Clear() 似乎可以解决问题。

      较早的设置retryTimesretryDelay 的建议对我不起作用,无论如何默认值是retryTimes = 10retryDelay = 100ms

      【讨论】:

        【解决方案6】:

        通过使用 Jeff Roe 的代码 (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
        }
        

        您能够以非常方便的方式处理错误。

        我已经设法通过使用System.Windows.Forms.Clipboard 而不是System.Windows.Clipboard 来减少错误发生的频率。

        我强调这并不能解决问题,但它减少了我的应用程序的发生率。

        【讨论】:

          【解决方案7】:

          这有点糟糕......但它解决了我的问题。

          延迟后重试 clear()。

          更多信息在博文中How to handle a blocked clipboard - Clipboard.Clear() error

          【讨论】:

          • 可能表明这是您的博文。
          【解决方案8】:

          我实际上已经想出了自己的解决方案,而且它似乎对我有用。

          // 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);
                  }
              }
          

          这显然是 C#,但是这些方法公开给所有 Visual Studio。我显然已经创建了一个循环函数,并试图通过重试将其强制进入剪贴板。

          基本上这是流程。假设您要将单词剪贴板放入剪贴板中,代码中的任何位置(假设已定义此函数)。

          1. 调用函数ClipboardPaste("Clipboard");
          2. 然后它将清除剪贴板
          3. 然后它将“尝试”将您的字符串放入剪贴板。
          4. 首先等待半秒(500 毫秒)
          5. 再次清除剪贴板
          6. 然后它尝试使用 SetDataObject 将字符串放入剪贴板
          7. SetDataObject 如果失败,最多会重试五次,每次重试之间有 250 毫秒的延迟。
          8. 如果初始尝试失败,它会捕获异常、崩溃,然后重新尝试。

          是的,如果您知道剪贴板无论如何都会有异常(无限循环),这确实存在缺陷。但是,我还没有使用这种方法陷入无限循环。另一个缺陷是它可能需要几秒钟(基本上会减慢您的应用程序)才能工作,而它正在尝试它可能会冻结您的应用程序,一旦成功,应用程序将继续运行。

          【讨论】:

          • 如果你的剪贴板设置总是导致异常,你不会得到无限循环,而是堆栈溢出异常,因为你在递归。
          猜你喜欢
          • 2011-01-27
          • 2014-02-03
          • 1970-01-01
          • 2012-08-25
          • 2014-05-26
          • 1970-01-01
          • 2017-12-08
          • 2014-08-03
          • 1970-01-01
          相关资源
          最近更新 更多