Как справиться с заблокированным буфером обмена и другими странностями

В течение последних нескольких часов я отслеживал довольно специфическую ошибку, которая возникает из-за того, что в другом приложении открыт буфер обмена. По сути, поскольку буфер обмена является общим ресурсом (согласно "Почему мой общий буфер обмена не работает?"), и вы пытаетесь выполнить

Clipboard.SetText(string)

or

Clipboard.Clear().

Выдается следующее исключение:

System.Runtime.InteropServices.ExternalException: Requested Clipboard operation did not succeed. 
    at System.Windows.Forms.Clipboard.ThrowIfFailed(Int32 hr)
    at System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 retryTimes, Int32 retryDelay)
    at System.Windows.Forms.Clipboard.SetText(String text, TextDataFormat format)
    at System.Windows.Forms.Clipboard.SetText(String text)

Мое первоначальное решение состояло в том, чтобы повторить попытку после короткой паузы, пока я не понял, что Clipboard.SetDataObject имеет поля для количества раз и продолжительности задержки. Поведение .NET по умолчанию — 10 попыток с задержкой в ​​100 мс.

Есть еще одна вещь, которую отметил конечный пользователь. То есть, несмотря на выброшенное исключение, операция копирования в буфер обмена все еще работает. Я не смог найти никакой дополнительной информации о том, почему это может быть.

Мое текущее решение проблемы - просто игнорировать исключение... действительно ли это лучший способ?


person Richard Slater    schedule 30.05.2009    source источник


Ответы (8)


Поскольку буфер обмена является общим для всех приложений пользовательского интерфейса, вы будете сталкиваться с этим время от времени. Очевидно, вы не хотите, чтобы ваше приложение аварийно завершало работу, если ему не удалось выполнить запись в буфер обмена, поэтому разумно изящно обрабатывать ExternalException. Я бы предложил представить пользователю сообщение об ошибке, если вызов SetObjectData для записи в буфер обмена не работает.

Предлагается использовать (через P/Invoke) user32!GetOpenClipboardWindow, чтобы узнать, открыт ли буфер обмена в другом приложении. Он вернет HWND окна, в котором открыт буфер обмена, или IntPtr.Zero, если ни одно приложение не открыло его. Вы можете вращать значение до тех пор, пока оно не станет IntPtr.Zero в течение определенного периода времени.

person Phil Price    schedule 30.05.2009
comment
Я читал о GetOpenClipboardWindow, кажется, что это лучшее решение проблем с доступом к буферу обмена. Спасибо за ответ. - person Richard Slater; 31.05.2009
comment
Как заставить процесс блокировать буфер обмена — см.: stackoverflow.com/questions/6583642/ - person toong; 13.09.2011
comment
Просто сначала закройте буфер обмена. Смотрите мой ответ. - person Triynko; 10.10.2012

Другой обходной путь — использовать Clipboard.SetDataObject вместо Clipboard.SetText.

Согласно этой статье MSDN, этот метод имеет два параметра: retryTimes и retryDelay, которые вы можете использовать следующим образом:

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
person Alex from Jitbit    schedule 05.03.2011
comment
Проблема, с которой я сталкиваюсь, заключается в том, что даже при больших числах как для retryTimes, так и для retryDelay (20/500), открытие Firefox по-прежнему приводит к сбою. Так странно... - person aaaidan; 30.08.2011
comment
Пожалуйста, не существует двух классов Clipboard в .NET framework. Один в пространстве имен System.Windows.Forms, а другой в пространстве имен System.Windows (для WPF). Только первый имеет перегрузку с количеством повторных попыток и задержкой. - person Maxence; 03.01.2016

Сегодня я столкнулся с этой ошибкой. Я решил справиться с этим, сообщив пользователю о потенциально плохом приложении. Для этого вы можете сделать что-то вроде этого:

[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». Я искал информацию об этом и был удивлен, что он дал только один ответ, и тот был на русском языке. Поэтому я добавляю этот ответ как для того, чтобы дать еще один ответ для этой строки, так и для предоставления дополнительной помощи, чтобы выявить потенциально плохо работающие приложения.

person Jeff Roe    schedule 12.10.2011
comment
В других подобных темах упоминалось, что существуют версии Skype, которые ведут себя довольно плохо, когда дело доходит до (общего) буфера обмена. - person cacau; 14.04.2014

Просто позвоните сначала:

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

Я заметил, что если вы находитесь в середине операции вставки (сообщение WM_PASTE), в том числе во время события TextChanged, буфер обмена остается заблокированным окном (текстовым полем), получающим событие. Поэтому, если вы просто вызываете этот метод CloseClipboard внутри обработчика событий, вы можете вызывать управляемые методы Clipboard.Clear и Clipboard.SetText без каких-либо проблем или задержек.

person Triynko    schedule 10.10.2012

Выполнение Clipboard.Clear() перед Clipboard.SetDataObject(pasteString, true), кажется, помогает.

Более раннее предложение установить retryTimes и retryDelay у меня не сработало, и в любом случае по умолчанию используются retryTimes = 10 и retryDelay = 100ms.

person Tony Bennett    schedule 25.05.2012

Используя код Джеффа Роу (Код Джеффа)

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

Я подчеркиваю, что это не устраняет проблему, но уменьшает количество случаев для моего приложения.

person Strider2009    schedule 24.07.2013

Это немного дерьмово... Но это решило мою проблему.

Повторите попытку очистки () после задержки.

Дополнительная информация находится в сообщении блога Как обработка заблокированного буфера обмена - ошибка Clipboard.Clear().

person Patrick Sameera    schedule 17.08.2011
comment
Возможно, покажите, что это ваш пост в блоге. - person Peter Mortensen; 17.03.2019

На самом деле я придумал свое собственное решение, и, похоже, оно работает для меня.

// 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. Я, очевидно, создал функцию зацикливания, а также попытался принудительно поместить ее в буфер обмена с повторными попытками.

По сути, вот поток. Допустим, вы хотите поместить слово clipboard в буфер обмена в любом месте вашего кода (при условии, что эта функция определена).

  1. Вызов функции ClipboardPaste("Буфер обмена");
  2. Затем он очистит буфер обмена
  3. Затем он «попытается» поместить вашу строку в буфер обмена.
  4. Сначала он ждет полсекунды (500 миллисекунд)
  5. Снова очищает буфер обмена
  6. Затем он пытается поместить строку в буфер обмена, используя SetDataObject.
  7. SetDataObject в случае сбоя будет повторять попытку до пяти раз с задержкой в ​​250 миллисекунд между каждой повторной попыткой.
  8. Если первоначальная попытка не удалась, он перехватывает исключение, сбой, а затем повторяет все заново.

Да, у этого есть недостаток, если вы знаете, что ваш буфер обмена всегда будет иметь исключение, несмотря ни на что (бесконечный цикл). Однако я еще не столкнулся с бесконечным циклом с этим методом. Другим недостатком является то, что это может занять пару секунд (по сути, замедляя ваши приложения), прежде чем оно заработает, в то время как оно пытается заморозить ваше приложение, как только оно будет успешным, приложение все равно продолжит работу.

person FlyingMongoose    schedule 04.03.2012
comment
Если ваш параметр буфера обмена всегда вызывает исключение, вы не получите бесконечный цикл, а исключение переполнения стека, потому что вы рекурсивно. - person dsolimano; 05.03.2012