Почему следующий код иногда вызывает исключение с содержимым «CLIPBRD_E_CANT_OPEN»:
Clipboard.SetText(str);
Обычно это происходит при первом использовании буфера обмена в приложении, а не после этого.
Это похоже на то, как MS реализовала это в Forms. Этот вопрос касался WPF (хотя я не понимал, что это имеет значение).





На самом деле, я думаю, это ошибка Win32 API.
Чтобы установить данные в буфер обмена, вы должны сначала Открой это. Буфер обмена может быть открыт только для одного процесса. Итак, когда вы проверяете, имеет ли другой процесс открытый буфер обмена по любой причине, ваша попытка открыть его потерпит неудачу.
Так уж получилось, что службы терминалов отслеживают буфер обмена, и в более старых версиях Windows (до Vista) вам нужно открыть буфер обмена, чтобы увидеть, что внутри ... что в конечном итоге блокирует вас. Единственное решение - дождаться, пока службы терминалов закроют буфер обмена, и повторить попытку.
Однако важно понимать, что это не относится к службам терминалов: это может случиться с чем угодно. Работа с буфером обмена в Win32 - это состояние гигантской гонки. Но, поскольку по замыслу вы должны возиться с буфером обмена только в ответ на ввод данных пользователем, это обычно не представляет проблемы.
Это вызвано ошибкой / функцией в буфере обмена служб терминалов (и, возможно, другими вещами) и реализацией буфера обмена .NET. Задержка открытия буфера обмена вызывает ошибку, которая обычно проходит в течение нескольких миллисекунд.
Решение состоит в том, чтобы попробовать несколько раз в цикле и засыпать между ними.
for (int i = 0; i < 10; i++)
{
try
{
Clipboard.SetText(str);
return;
}
catch { }
System.Threading.Thread.Sleep(10);
}
Если вы посмотрите на внутреннее устройство Clipboard.SetText, по крайней мере, в .NET 2.0 SP1, вы увидите, что у него уже есть цикл повторных попыток / ожидания. Повторяется до 10 раз с задержкой 100 мс.
@Mike: System.Windows.Forms.Clipboard имеет повторную попытку, а System.Windows.Clipboard из WPF - нет.
Это безумие, чистое безумие ...: D Я потратил 2 часа без какой-либо ссылки, пытаясь выяснить, как заставить эту чертову штуку работать, и вы говорите мне, что я должен был просто попробовать, ДО ТОГО, как это сработало в кровавом цикле? Безумие! :)
catch {} - плохая практика. Заменить на catch (COMException ex) { const uint CLIPBRD_E_CANT_OPEN = 0x800401D0; if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN) throw; }
Действительно, в буфере обмена WinForms есть повторная попытка, видимый здесь
На самом деле может возникнуть другая проблема. Вызов фреймворка (варианты 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 и менеджер загрузок), которые прослушивают событие буфера обмена. Как только сработает первый вызов, приложение откроет буфер обмена для просмотра данных, а второй вызов сброса завершится ошибкой.
Не нашел хорошего решения, кроме как написать свой собственный класс буфера обмена, который использует прямой API Win32 или вызвать setDataObject напрямую с false для хранения данных после закрытия приложения.
Я решил эту проблему для своего собственного приложения, используя собственные функции Win32: OpenClipboard (), CloseClipboard () и SetClipboardData ().
Ниже созданного мной класса-оболочки. Может ли кто-нибудь пожалуйста просмотреть его и скажи, правильно это или нет. Особенно, когда управляемый код работает как приложение x64 (я использую Any CPU в параметрах проекта). Что происходит, когда я подключаюсь к библиотекам x86 из приложения x64?
Спасибо!
Вот код:
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() перед вызовом «родной» функции (т.е. использовал собственный способ, только если управляемый не работал). Но если управляемая версия терпит неудачу, она блокирует буфер обмена, а после этой собственной версии также не удается открыть буфер обмена.
Я знаю, что это старый вопрос, но проблема все еще существует. Как упоминалось ранее, это исключение возникает, когда системный буфер обмена заблокирован другим процессом. К сожалению, существует множество инструментов для вырезания, программ для создания снимков экрана и инструментов для копирования файлов, которые могут блокировать буфер обмена Windows. Таким образом, вы будете получать исключение каждый раз, когда попытаетесь использовать Clipboard.SetText(str), когда такой инструмент установлен на вашем ПК.
Решение:
никогда не использовать
Clipboard.SetText(str);
использовать вместо
Clipboard.SetDataObject(str);
@K_Rol: Похоже, это объясняет ответ Ишая Галацера.
Я получаю сообщение «Значение типа String не может быть преобразовано в DataObject»?
К вашему сведению, мне пришлось использовать Clipboard.SetDataObject(str, true);, чтобы данные буфера обмена были доступны вне приложения.
Это случилось со мной в моем приложении 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
Используйте версию WinForms (да, использование WinForms в приложениях WPF не вредно), она обрабатывает все, что вам нужно:
System.Windows.Forms.SetDataObject(yourText, true, 10, 100);
Это попытается скопировать ваш текст в буфер обмена, он останется после того, как ваше приложение существует, будет пытаться до 10 раз и будет ждать 100 мс между каждой попыткой.
Это довольно сложное решение - действительно ли это единственный способ?