简体   繁体   English

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

[英]OpenClipboard failed when copy pasting data from WPF DataGrid

I've got a WPF application using datagrid.我有一个使用 datagrid 的 WPF 应用程序。 The application worked fine until I installed Visual Studio 2012 and Blend+SketchFlow preview.该应用程序运行良好,直到我安装了 Visual Studio 2012 和 Blend+SketchFlow 预览版。 Now, when I'm trying to copy the data from the grid into the clipboard with Ctrl + C (in any application), I'm getting the following exception:现在,当我尝试使用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()

This is really annoying.这真的很烦人。

I've seen some references to this problem here and on various locations on the web, with no real solution.我在这里和网络上的各个位置都看到了对这个问题的一些参考,但没有真正的解决方案。

I can verify that the clipboard is locked when this exception is raised in Visual Studio, as I couldn't copy paste the message (had to write it to a file).在 Visual Studio 中引发此异常时,我可以验证剪贴板是否已锁定,因为我无法复制粘贴消息(必须将其写入文件)。 Also, the clipboard wasn't locked before the copy process started.此外,剪贴板在复制过程开始之前没有被锁定。

How to solve this problem?如何解决这个问题呢?

We are using .NET 4.0.我们正在使用 .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.我再也没有遇到过这个问题。

It is a bug in the WPF Clipboard handler.这是 WPF 剪贴板处理程序中的一个错误。 You need to handle the unhandled exception in the Application.DispatcherUnhandledException event.您需要在 Application.DispatcherUnhandledException 事件中处理未处理的异常。

Add this attribute to the Application element in your App.xaml将此属性添加到 App.xaml 中的Application元素

DispatcherUnhandledException="Application_DispatcherUnhandledException"

Add this code to your App.xaml.cs file将此代码添加到您的 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;
}

I, too, have been having a problem in an application where I copy information into the clipboard as users peruse a ListBox.我也在应用程序中遇到了问题,当用户细读 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.有时我会在某些用户的系统上获得 CLIBRD_E_CANT_OPEN,但在其他用户的系统上却没有。

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.我将添加我创建的using语句、属性和方法来查找罪魁祸首的Process对象。 From the Process item you can obtain the process' name, PID, main window title (if it has one), and other potentially useful data.Process项中,您可以获得进程的名称、PID、主窗口标题(如果有的话)和其他可能有用的数据。 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.其他注意事项:我更改的另一件事简化了我的代码是从使用System.Windows.Clipboard转换为System.Windows.Forms.Clipboard (请参阅System.Windows.Forms.Clipboard Class ),因为后者有一个 4-参数SetDataObject()方法,其中包括重试计数和重试延迟(以毫秒为单位)。 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 also had this issue in WPF 4.0 and 4.5 since I installed TeraCopy (Windows 7, 64-bit).我在 WPF 4.0 和 4.5 中也遇到过这个问题,因为我安装了TeraCopy (Windows 7,64位)。 Every Clipboard.SetText() failed with a System.Runtime.InteropServices.COMException.每个 Clipboard.SetText() 都因 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.我的第一个解决方案是卸载 TeraCopy - 它有效,但我喜欢这个应用程序,所以我不得不寻找另一个解决方案来解决这个问题。 The solution was to replace解决方案是更换

Clipboard.SetText("my string");

with

Clipboard.SetDataObject("my string");

I had the same problem with RichTextBox.我在 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似乎首选使用System.Windows.Controls.RichTextBox.Copy

I had a problem to retrieve XAML data from the clipboard with .NET 4.6.1.我在使用 .NET 4.6.1 从剪贴板检索 XAML 数据时遇到问题。

Error message:错误信息:

OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN))) OpenClipboard 失败(来自 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 { }
}

I have finaly found a solution to use the default copy mode implemented by DataGrid.我终于找到了使用 DataGrid 实现的默认复制模式的解决方案。

The previous answers didn't work for me:以前的答案对我不起作用:

  • Using Clipboard.SetDataObject(data);使用 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.代替 Clipboard.SetText(data) --> 这个解决方案不是我所期望的,我不想自己实现复制功能。
  • Handling DispatcherUnhandledException : i do not know why but it didn't work for me.处理 DispatcherUnhandledException :我不知道为什么,但它对我不起作用。 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".您只需要在按“Ctrl + C”之前清除剪贴板。

So, i made a new style in the MainWindows.xaml file resources:因此,我在 MainWindows.xaml 文件资源中创建了一个新样式:

<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.此样式用于处理我的应用程序的所有数据网格中的“previewKeyDown”。 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.之后,问题就解决了。

I had the same problem in copying Excel cells to the clipboard and getting data from the clipboard as an HTML string.我在将 Excel 单元格复制到剪贴板并从剪贴板获取数据作为 HTML 字符串时遇到了同样的问题。

You can use (while-try-catch) like in the below code.您可以像下面的代码一样使用 (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;
    }
}

Also, you can have a counter in the while if the loop is more than 10 times or more, exception occur.此外,如果循环次数超过 10 次或更多,您可以在while设置一个计数器,则会发生异常。 I test its maximum counter is one and in one time loop clipboard work.我测试它的最大计数器是一且一次循环剪贴板工作。

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) .有一个 DataGrid 事件/方法签名用于这个确切目的CopyingRowClipboardContentobject senderDataGridRowClipboardEventArgs e)并且比Clipboard.SetDataObject(data)Clipboard.SetText(data)更可靠。

Here's how to use it.这是如何使用它。

Set "FullRow" at the SelectionUnit mode for dataGrid called myDataGrid在名为 myDataGrid 的 dataGrid 的 SelectionUnit 模式下设置“FullRow”

<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.我们有一个方法myDataGrid_CopyingRowClipboardContent ,它被调用来为 dataGrid 中的每一行将其内容复制到剪贴板。 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;
}

Code app.xaml代码 app.xaml

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

code file app.xaml.cs代码文件 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):我为 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”是您的数据网格的名称,请使用一行代码调用自己的用户控件。

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.为您的数据网格。

That worked for me too. 这对我也有用。

Change the class from: 更改班级:

Clipboard.SetText("my string");

to: 至:

Clipboard.SetDataObject("my string");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM