Помилка OpenClipboard під час копіювання вставки даних із WPF DataGrid


79

У мене є програма WPF за допомогою datagrid. Додаток працював нормально, доки я не встановив Visual Studio 2012 та попередній перегляд Blend + SketchFlow. Тепер, коли я намагаюся скопіювати дані з сітки в буфер обміну за допомогою Ctrl+ C(у будь-якому додатку), я отримую наступний виняток:

System.Runtime.InteropServices.COMException (0x800401D0): OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN))
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode, IntPtr errorInfo)
   at System.Windows.Clipboard.Flush()
   at System.Windows.Clipboard.CriticalSetDataObject(Object data, Boolean copy)
   at System.Windows.Controls.DataGrid.OnExecutedCopy(ExecutedRoutedEventArgs args)
   at System.Windows.Controls.DataGrid.OnExecutedCopy(Object target, ExecutedRoutedEventArgs args)
   at System.Windows.Input.CommandBinding.OnExecuted(Object sender, ExecutedRoutedEventArgs e)
   at System.Windows.Input.CommandManager.ExecuteCommandBinding(Object sender, ExecutedRoutedEventArgs e, CommandBinding commandBinding)
   at System.Windows.Input.CommandManager.FindCommandBinding(CommandBindingCollection commandBindings, Object sender, RoutedEventArgs e, ICommand command, Boolean execute)
   at System.Windows.Input.CommandManager.FindCommandBinding(Object sender, RoutedEventArgs e, ICommand command, Boolean execute)
   at System.Windows.Input.CommandManager.OnExecuted(Object sender, ExecutedRoutedEventArgs e)
   at System.Windows.UIElement.OnExecutedThunk(Object sender, ExecutedRoutedEventArgs e)
   at System.Windows.Input.ExecutedRoutedEventArgs.InvokeEventHandler(Delegate genericHandler, Object target)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
   at System.Windows.Input.RoutedCommand.ExecuteImpl(Object parameter, IInputElement target, Boolean userInitiated)
   at System.Windows.Input.RoutedCommand.ExecuteCore(Object parameter, IInputElement target, Boolean userInitiated)
   at System.Windows.Input.CommandManager.TranslateInput(IInputElement targetElement, InputEventArgs inputEventArgs)
   at System.Windows.UIElement.OnKeyDownThunk(Object sender, KeyEventArgs e)
   at System.Windows.Input.KeyEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndKeyboardInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawKeyboardActions actions, Int32 scanCode, Boolean isExtendedKey, Boolean isSystemKey, Int32 virtualKey)
   at System.Windows.Interop.HwndKeyboardInputProvider.ProcessKeyAction(MSG& msg, Boolean& handled)
   at System.Windows.Interop.HwndSource.CriticalTranslateAccelerator(MSG& msg, ModifierKeys modifiers)
   at System.Windows.Interop.HwndSource.OnPreprocessMessage(Object param)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)
   at System.Windows.Interop.HwndSource.OnPreprocessMessageThunk(MSG& msg, Boolean& handled)
   at System.Windows.Interop.HwndSource.WeakEventPreprocessMessage.OnPreprocessMessage(MSG& msg, Boolean& handled)
   at System.Windows.Interop.ComponentDispatcherThread.RaiseThreadMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()

Це справді дратує.

Я бачив деякі посилання на цю проблему тут і в різних місцях в Інтернеті, без реального рішення.

Я можу перевірити, що буфер обміну заблокований, коли цей виняток викликано у Visual Studio, оскільки я не зміг скопіювати вставку повідомлення (довелося записати його у файл). Крім того, буфер обміну не був заблокований до початку копіювання.

Як вирішити цю проблему?


Проблема насправді проявляється у Visual Studio 2019 під час спроби скопіювати файл sln у точці, де ви бачите нещодавно відкриті проекти під час запуску IDE.
JGFMK

Відповіді:


101

Ми використовуємо .NET 4.0. У нас була та сама проблема, але після виходу з системи код деякий час працював нормально.

Нарешті ми знайшли альтернативу.

Якщо ви хочете скопіювати рядок у буфер обміну,

string data = "Copy This"

Дотепер я використовував наступний метод

Clipboard.SetText(data);

Він зазнавав невдач знову і знову. Потім я подивився на інші методи, доступні для встановлення тексту в буфер обміну в Clipboard Class, і спробував наступне:

Clipboard.SetDataObject(data);

І це спрацювало :). Я більше ніколи не мав проблеми.


5
Дуже добре. Я використовую .NET 4.5, і проблема все ще існує. Здається, що якщо я повторно вибираю елемент у ListView, тоді ця помилка трапляється, але я не уявляю, чому.
Alois Kraus

2
Я використовую WPF та .NET 4.5 (а не 4.5.1), і проблема вирішена з цим рішенням! :-)
Марсель,

1
SetDataObject тут добре працював. SetText видає виняток, а інший - ні. Дякую!
Фабіано

Я виявив, що цей дзвінок чудово працює з кількома копіями буфер обміну мереж даних.
sailfish009 02

78

Це помилка в обробнику буфера обміну WPF. Вам потрібно обробити необроблений виняток у події Application.DispatcherUnhandledException.

Додайте цей атрибут до Applicationелемента у вашому App.xaml

DispatcherUnhandledException="Application_DispatcherUnhandledException"

Додайте цей код у файл App.xaml.cs

void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
    var comException = e.Exception as System.Runtime.InteropServices.COMException;

    if (comException != null && comException.ErrorCode == -2147221040)
         e.Handled = true;
}

5
Корпорація Майкрософт виправила це в .NET 4.5, код тепер подібний до коду, який міститься у збірках Windows Forms. Підпрограми буфера обміну мають час повторної спроби та час затримки замість того, щоб просто провалитись, як тільки буфер обміну не вдається отримати доступ.
Alex Wiese

2
Я використовую .NET 4.0 і стикаюся з тією ж проблемою. Я не хочу торкатися файлів App.xaml. Чи є якесь інше рішення цієї проблеми ???
kushdilip

2
Чому ви не хочете торкатися файлів app.xaml? Ви можете підписатися на Application.DispatcherUnhandledExceptionподію з іншого місця, але ви повинні робити це у своєму класі програми, коли програма завантажується. Більше інформації тут
Alex Wiese

6
@kushdilip так, це рішення чудово, коли ви контролюєте метод, який встановлює дані буфера обміну, але це не допомагає, коли методу немає у вашому коді. Наприклад, елементи керування WPF та елементи керування сторонніх виробників. Якщо ви десь не застосуєте виправлення помилки у своєму коді, це все одно вплине на вас у .NET 4.0.
Alex Wiese

6
SetText НЕ виправлений у 4.5, але використання SetDataObject, здається, це виправляє - ОДНО: моя програма копіює шлях до папки до буфера обміну. Це працює нормально, але якщо я запустив додаток у налагоджувачі VS і перетворив виняток .NET на те, що відбувається щось дивне. Якщо я вставляю, скажімо, у Блокнот, він працює нормально, але якщо я вставляю у вікно розташування Провідника Windows, моя програма видає COM-виняток "Недійсна структура FORMATETC (Виняток із HRESULT: 0x80040064 (DV_E_FORMATETC))". Це трапляється лише під час налагодження з винятком, що ловить, тому виняток потрібно десь вловити та обробити, але це все одно дивно.
Дейв

7

Я теж маю проблему в програмі, коли я копіюю інформацію в буфер обміну, коли користувачі переглядають ListBox. Інформація, яка скопіюється, пов’язана з вибраним елементом, і це дозволяє їм вставити її (зазначену інформацію) в інші програми для зручності. Іноді я отримую CLIPBRD_E_CANT_OPEN в одних системах користувачів, але не в інших.

Хоча я все ще не зміг виправити суперечку, мені вдалося створити код для пошуку програми, яка спричиняє цю суперечку. Я хотів би хоча б поділитися цим кодом, сподіваючись, що він комусь допомагає. Я додаю оператор using , атрибути та метод, який я створив, щоб знайти об'єкт Process винуватця. З елемента Process ви можете отримати ім'я процесу, PID, заголовок головного вікна (якщо він є) та інші потенційно корисні дані. Ось рядки коду, які я додав без коду, який його викликає. ( ПРИМІТКА. Під фрагментом коду я маю поділитися ще одним лакомством):

using System.Diagnostics;               // For Process class
using System.Runtime.InteropServices;   // For DllImport's

...

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

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

...

    ///-----------------------------------------------------------------------------
    /// <summary>
    /// Gets the Process that's holding the clipboard
    /// </summary>
    /// <returns>A Process object holding the clipboard, or null</returns>
    ///-----------------------------------------------------------------------------
    public Process ProcessHoldingClipboard()
    {
        Process theProc = null;

        IntPtr hwnd = GetOpenClipboardWindow();

        if (hwnd != IntPtr.Zero)
        {
            uint processId;
            uint threadId = GetWindowThreadProcessId(hwnd, out processId);

            Process[] procs = Process.GetProcesses();
            foreach (Process proc in procs)
            {
                IntPtr handle = proc.MainWindowHandle;

                if (handle == hwnd)
                {
                    theProc = proc;
                }
                else if (processId == proc.Id)
                {
                    theProc = proc;
                }
            }
        }

        return theProc;
    }

ІНШЕ ПРИМІТКА: Я змінив ще одну річ, яка трохи спростила мій код, це перетворити з використання System.Windows.Clipboard на System.Windows.Forms.Clipboard (див. Клас System.Windows.Forms.Clipboard ), оскільки останній має 4- параметр SetDataObject (), який включає кількість повторних спроб та затримку повторних спроб у мілісекундах. Це принаймні видалило частину шуму повторної спроби з мого коду.

Ваш пробіг може відрізнятися ... плюс у цьому можуть бути побічні ефекти, про які я ще не наткнувся, тому, якщо хтось про них знає, будь ласка, коментуйте. У будь-якому випадку, я сподіваюся, це комусь стане в нагоді.


2
+1 за використання System.Windows.Forms.Clipboard замість System.Windows.Clipboard. подяка
Dmase05

6

У мене також була ця проблема в WPF 4.0 та 4.5, оскільки я встановив TeraCopy (Windows 7, 64-розрядна версія). Кожен Clipboard.SetText () не вдався із System.Runtime.InteropServices.COMException.

Моїм першим рішенням було видалити TeraCopy - це спрацювало, але мені подобається цей додаток, тому мені довелося шукати інше рішення для вирішення цієї проблеми. Рішенням було замінити

Clipboard.SetText("my string");

з

Clipboard.SetDataObject("my string");

Недоліком є ​​те, що дані будуть видалятися із системного буфера обміну при виході програми.
пагорб

1
Ні, якщо ви встановите другий параметр true, щоб залишався даними після виходу програми. Перевірте це: docs.microsoft.com/de-de/dotnet/api/…
pr0gg3r

2

У мене була та ж проблема з RichTextBox. Наступний код випадково розбився:

TextRange tr = new TextRange(rich.Document.ContentStart, rich.Document.ContentEnd);
System.Windows.Clipboard.SetDataObject(tr.Text);

Здається, переважно використовувати System.Windows.Controls.RichTextBox.Copy


2

У мене виникла проблема з отриманням даних XAML із буфера обміну за допомогою .NET 4.6.1.

Повідомлення про помилку:

Помилка OpenClipboard (Виняток з HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN)))

Я вирішив це наступним чином:

int counter = 0;
object xamlClipData = null;

while (xamlClipData == null)
{
    try
    {
        if (counter > 10)
        {
            System.Windows.MessageBox.Show("No access to clipboard xaml data.");
            break;
        }

        counter++;

        if (System.Windows.Clipboard.GetDataObject().GetDataPresent(DataFormats.Xaml))
        {
            xamlClipData = System.Windows.Clipboard.GetData(DataFormats.Xaml);
        }
    }
    catch { }
}

1

У мене була та ж проблема при копіюванні комірок Excel у буфер обміну та отриманні даних із буфера обміну, як рядок HTML.

Ви можете використовувати (while-try-catch), як у наведеному нижче коді.

Excel.Application exap = new Microsoft.Office.Interop.Excel.Application();
Excel.Workbook wb = exap.Workbooks.Open(
                      sourceFileNameTextBox.Text,
                      Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                      Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                      Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                      Type.Missing, Type.Missing);
Excel.Sheets sh = wb.Worksheets;

bool clip = false;

// Copy Excel cells to clipboard
while (!clip)
{
    try
    {
        ws.Cells.get_Range(cells[0], cells[1]).Copy(Type.Missing);
        clip = true;
    }
    catch
    {
        clip = false;
    }
}

string b = "";

// Get Excel cells data from the clipboard as HTML

clip = false;
while(!clip)
{
    try
    {
        b = Clipboard.GetData(DataFormats.Html) as string;
        clip = true;
    }
    catch
    {
        clip = false;
    }
}

Крім того, у вас може бути лічильник, whileякщо цикл перевищує 10 і більше разів, виникає виняток. Я перевіряю, що його максимальний лічильник - один і за один раз працює в буфер обміну.


1

Я остаточно знайшов рішення використовувати режим копіювання за замовчуванням, реалізований DataGrid.

Попередні відповіді для мене не працювали:

  • Використання Clipboard.SetDataObject (дані); за допомогою Clipboard.SetText (дані) -> Це рішення було не таким, як я очікував, я не хотів реалізовувати функцію копіювання.
  • Обробка DispatcherUnhandledException: я не знаю чому, але у мене це не спрацювало. Метод, приєднаний до цієї події, не викликався.

Нарешті я знайшов новий спосіб вирішення цієї проблеми. Вам просто потрібно очистити буфер обміну, перш ніж натискати "Ctrl + C".

Отже, я створив новий стиль у файлових ресурсах MainWindows.xaml:

<Window.Resources>
    <Style TargetType="DataGrid">
        <EventSetter Event="PreviewKeyDown" Handler="DataGrid_PreviewKeyDown"/>
    </Style>
</Window.Resources>

Цей стиль створений для обробки "previewKeyDown" у всіх сітках даних мого додатка. Викликається такий метод:

private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.C && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
    {
        System.Windows.Forms.Clipboard.Clear();
    }
}

Після цього проблема була вирішена.


0

Для цієї мети існує підпис події / методу DataGrid CopyingRowClipboardContent( object sender, DataGridRowClipboardEventArgse), який є надійнішим, ніж Clipboard.SetDataObject(data)або Clipboard.SetText(data).

Ось як ним користуватися.

Встановіть "FullRow" у режимі SelectionUnit для dataGrid під назвою myDataGrid

<DataGrid x:Name="myDataGrid" SelectionUnit="FullRow"></DataGrid>

У нас є метод, myDataGrid_CopyingRowClipboardContentякий викликається для кожного рядка в dataGrid для копіювання його вмісту в буфер обміну. Наприклад, для сітки даних із семи рядків це називається сім разів.

public int clipboardcalledcnt { get; set; } // CopyingRowClipboardContent invoked count
private void myDataGrid_CopyingRowClipboardContent(object sender, DataGridRowClipboardEventArgs e)
{
    PathInfo cellpath = new PathInfo(); // A custom class to hold path information
    string path = string.Empty;

    DataGrid dgdataPaths = (DataGrid)sender;
    int rowcnt = dgdataPaths.SelectedItems.Count;

    cellpath = (PathInfo)e.Item;

    path = "Row #" + clipboardcalledcnt + " Len=" + cellpath.Length.ToString() + ", path=" + cellpath.Path;

    e.ClipboardRowContent.Clear();

    if (clipboardcalledcnt == 0) // Add header to clipboard paste
        e.ClipboardRowContent.Add(new DataGridClipboardCellContent("", null, "--- Clipboard Paste ---\t\t\n")); // \t cell divider, repeat (number of cells - 1)

    clipboardcalledcnt++;
    e.ClipboardRowContent.Add(new DataGridClipboardCellContent(path, null, path));

    if (clipboardcalledcnt == rowcnt)
        clipboardcalledcnt = 0;
}

0

Код app.xaml

<Application.Resources>
        <Style TargetType="DataGrid">
            <EventSetter Event="PreviewKeyDown" Handler="DataGrid_PreviewKeyDown"/>
        </Style>
    </Application.Resources>

кодовий файл app.xaml.cs

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.C && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                System.Windows.Forms.Clipboard.Clear();
            }
        }
    }
}

Я мав справу з цим кодом.


Будь ласка, вдосконаліть свою відповідь, відредагувавши її кращим чином, ніж зараз.
kwoxer

0

Я пишу метод розширення для експорту даних WPF в Excel (CSV):

якщо "MyDatagrid" - це ім'я вашої сітки даних, використовуйте один рядок для виклику на власному контролі користувача.

MyDatagrid.ExportToExcel(this);

і додайте цей метод до статичного класу вашого розширення

#region DataGrid Extentions

public static void ExportToExcel(this DataGrid dg, UserControl owner, string filename = "")
{
    try
    {
        dg.SelectionMode = DataGridSelectionMode.Extended;
        dg.SelectAllCells();

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

        var saveFileDialog = new SaveFileDialog
        {
            FileName = filename != "" ? filename : "gpmfca-exportedDocument",
            DefaultExt = ".csv", 
            Filter = "Common Seprated Documents (.csv)|*.csv"
        };

        if (saveFileDialog.ShowDialog() == true)
        {
            var clip2 = Clipboard.GetText();
            File.WriteAllText(saveFileDialog.FileName, clip2.Replace('\t', ','), Encoding.UTF8);
            Process.Start(saveFileDialog.FileName);
        }    
   
        dg.UnselectAllCells();
        dg.SelectionMode = DataGridSelectionMode.Single;
    }
    catch (Exception ex)
    {
        owner.ShowMessageBox(ex.Message);
        Clipboard.Clear();
    }
}
#endregion

нарешті, не забудь

using Microsoft.Win32;

на розширення класу та набору

ClipboardCopyMode="IncludeHeader"

для вашої сітки даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.