53

在过去的几个小时里,我一直在追踪一个相当具体的错误,因为另一个应用程序打开了剪贴板。本质上,剪贴板是共享资源(根据“为什么我的共享剪贴板不起作用?”)并且您尝试执行

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 的默认行为是以 100 毫秒的延迟尝试 10 次。

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

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

4

8 回答 8

44

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

根据这篇 MSDN 文章,此方法有两个参数 - 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
于 2011-03-05T12:32:51.157 回答
28

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

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

于 2009-05-30T18:11:59.583 回答
12

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

[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”。我搜索了这方面的信息,很惊讶它只命中了一个,而且是俄语的。因此,我添加了这个答案,既是为了对该字符串再次进行打击,又是为了提供进一步的帮助,以使可能出现行为异常的应用程序曝光。

于 2011-10-12T22:34:50.803 回答
4

做一个Clipboard.Clear()beforeClipboard.SetDataObject(pasteString, true)似乎可以解决问题。

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

于 2012-05-25T13:58:06.070 回答
4

只需先调用它:

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

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

于 2012-10-10T19:07:42.723 回答
3

通过使用 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.

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

于 2013-07-24T13:03:25.610 回答
0

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

延迟后重试 clear()。

更多信息在博客文章如何处理被阻止的剪贴板 - Clipboard.Clear() 错误

于 2011-08-17T05:47:00.970 回答
-4

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

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

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

于 2012-03-04T21:28:11.877 回答