从 WPF DataGrid 复制粘贴数据时,OpenClipboard 失败

我有一个使用 datagrid 的 WPF 应用程序。在安装 VisualStudio2012和 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()

这真的很烦人。

我已经看到了一些关于这个问题 给你和在网络上的各种位置的参考,没有真正的解决方案。

在 VisualStudio 中引发此异常时,我可以验证剪贴板是否已锁定,因为我无法复制粘贴消息(必须将其写入文件)。此外,剪贴板在复制过程开始之前没有锁定。

如何解决这个问题?

58954 次浏览

It is a bug in the WPF Clipboard handler. You need to handle the unhandled exception in the Application.DispatcherUnhandledException event.

Add this attribute to the Application element in your App.xaml

DispatcherUnhandledException="Application_DispatcherUnhandledException"

Add this code to your App.xaml.cs file

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

We are using .NET 4.0. We had the same problem, but after logging off the system, code used to work fine for some time.

Finally we found the alternative.

If you want to copy a string to the clipboard,

string data = "Copy This"

Till now I was using the following method

Clipboard.SetText(data);

It was failing again and again. Then I looked at other methods available to set text in the clipboard in Clipboard Class and tried the following:

Clipboard.SetDataObject(data);

And it worked :). I never had the issue again.

I, too, have been having a problem in an application where I copy information into the clipboard as users peruse a ListBox. The information that's copied is related to the selected item, and it permits them to paste it (said info) into other applications for convenience. Occasionally I get the CLIPBRD_E_CANT_OPEN on some user's systems, but not on others.

While I still haven't been able to fix the contention, I was able to create some code to find the application causing the contention. I'd like to at least share this code in the hope it helps someone. I will add the using statement, attributes, and method I created to find the Process object of the culprit. From the Process item you can obtain the process' name, PID, main window title (if it has one), and other potentially useful data. Here's the lines of code I added without the code that calls it. (NOTE: Below the code snippet I have one more tidbit to share):

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

OTHER NOTE: One other thing I changed which simplified my code a bit was to convert from using System.Windows.Clipboard to System.Windows.Forms.Clipboard (see System.Windows.Forms.Clipboard Class)because the latter has a 4-parameter SetDataObject() method which includes a retry count and a retry delay in milliseconds. This at least removed some of the retry noise from my code.

Your mileage may vary...plus there may be side effects in this which I've not yet stumbled upon, so if anyone knows of them please comment. In any event, I hope this proves useful to someone.

I had the same problem with RichTextBox. The following code crashed randomly:

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

It seems it's preferred to use System.Windows.Controls.RichTextBox.Copy

I also had this issue in WPF 4.0 and 4.5 since I installed TeraCopy (Windows 7, 64-bit). Every Clipboard.SetText() failed with a System.Runtime.InteropServices.COMException.

My first solution was to uninstall TeraCopy - it worked, but I love this application, so I had to search for another solution to resolve that issue. The solution was to replace

Clipboard.SetText("my string");

with

Clipboard.SetDataObject("my string");

I had the same problem in copying Excel cells to the clipboard and getting data from the clipboard as an HTML string.

You can use (while-try-catch) like in the below code.

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

Also, you can have a counter in the while if the loop is more than 10 times or more, exception occur. I test its maximum counter is one and in one time loop clipboard work.

I had a problem to retrieve XAML data from the clipboard with .NET 4.6.1.

Error message:

OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN)))

I solved it as follows:

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

There's a DataGrid event/method signature for this exact purpose CopyingRowClipboardContent(object sender, DataGridRowClipboardEventArgs e) and is more reliable than Clipboard.SetDataObject(data) or Clipboard.SetText(data).

Here's how to use it.

Set "FullRow" at the SelectionUnit mode for dataGrid called myDataGrid

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

We have a method, myDataGrid_CopyingRowClipboardContent, that gets called for each row in the dataGrid to copy its contents to the clipboard. For example,for a datagrid with seven rows this is called seven times.

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

I have finaly found a solution to use the default copy mode implemented by DataGrid.

The previous answers didn't work for me:

  • Using Clipboard.SetDataObject(data); insteed of Clipboard.SetText(data) --> This solution was not what i expected, i didn't want to implement myself the copy feature.
  • Handling DispatcherUnhandledException : i do not know why but it didn't work for me. The method attached to this event was not called.

I finally found a new way to handle this problem. You just need to clear the clipboard before you press "Ctrl + C".

So, i made a new style in the MainWindows.xaml file resources:

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

This style is made to handle the "previewKeyDown" in all datagrids of my application. The method called is the following:

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

After that, the problem was solved.

Code app.xaml

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

code file 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();
}
}
}
}

I have dealt with this code.

I write a extension method for WPF Datagrid Export to Excel(CSV):

if "MyDatagrid" is the name of your datagrid, use one line code to call on own user control.

MyDatagrid.ExportToExcel(this);

and add this method to your extension static class

#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

finally don't forget to

using Microsoft.Win32;

on extension class and set

ClipboardCopyMode="IncludeHeader"

for your datagrid.