【问题标题】:OpenClipboard failed when copy pasting data from WPF DataGrid从 WPF DataGrid 复制粘贴数据时,OpenClipboard 失败
【发布时间】:2012-09-27 23:14:14
【问题描述】:

我有一个使用数据网格的 WPF 应用程序。在我安装 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()

这真的很烦人。

我在网络上的不同位置看到了一些关于这个问题here 的引用,但没有真正的解决方案。

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

如何解决这个问题?

【问题讨论】:

  • 当您尝试在启动 IDE 时看到最近打开的项目的位置复制 sln 文件时,问题实际上在 Visual Studio 2019 中表现出来。

标签: c# wpf datagrid clipboard copy-paste


【解决方案1】:

我们正在使用 .NET 4.0。我们遇到了同样的问题,但是在注销系统后,代码过去可以正常运行一段时间。

我们终于找到了替代方案。

如果要将字符串复制到剪贴板,

string data = "Copy This"

直到现在我都在使用以下方法

Clipboard.SetText(data);

它一次又一次地失败了。然后我在 Clipboard Class 中查看了可用于在剪贴板中设置文本的其他方法,并尝试了以下方法:

Clipboard.SetDataObject(data);

它奏效了:)。我再也没有遇到过这个问题。

【讨论】:

  • 非常好。我使用 .NET 4.5,但问题仍然存在。似乎如果我在 ListView 中重新选择一个项目,则确实会发生此错误,但我不知道为什么。
  • 我使用 WPF 和 .NET 4.5(不是 4.5.1),这个解决方案解决了问题! :-)
  • SetDataObject 在这里运行良好。 SetText 抛出一个异常,但另一个没有。谢谢!
  • 我发现此调用适用于数据网格的多个剪贴板副本。
【解决方案2】:

这是 WPF 剪贴板处理程序中的一个错误。您需要在 Application.DispatcherUnhandledException 事件中处理未处理的异常。

将此属性添加到 App.xaml 中的 Application 元素

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

【讨论】:

  • Microsoft 已在 .NET 4.5 中修复此问题,现在代码类似于 Windows 窗体程序集中的代码。剪贴板例程具有重试计数和延迟时间,而不是在剪贴板无法访问时立即失败。
  • 我正在使用 .NET 4.0 并面临同样的问题。我不想触摸 App.xaml 文件。这个问题还有其他解决办法吗???
  • 为什么不想触摸 app.xaml 文件?您可以从其他地方订阅Application.DispatcherUnhandledException 事件,但您应该在您的应用加载时在您的 App 类中进行。更多信息here
  • @kushdilip 是的,当您控制设置剪贴板数据的方法时,该解决方案很好,但当该方法不在 您的 代码中时,它没有帮助。例如 WPF 控件和第三方控件。如果您没有在代码中的某个地方实现错误修复,您仍然会在 .NET 4.0 上受到它的影响。
  • SetText 在 4.5 中未修复,但使用 SetDataObject 似乎可以修复它 - 但是:我的程序将文件夹路径复制到剪贴板。它工作正常,但是如果我在 VS 调试器中运行该应用程序并打开 .NET 异常,就会发生一些奇怪的事情。如果我粘贴到记事本中,它可以正常工作,但是如果我粘贴到 Windows 资源管理器的位置框中,我的应用程序会引发 COM 异常“无效的 FORMATETC 结构(来自 HRESULT 的异常:0x80040064 (DV_E_FORMATETC))”。这只发生在有异常捕获的调试时,所以必须在某个地方捕获和处理异常,但这仍然很奇怪。
【解决方案3】:

我在应用程序中也遇到了问题,我在用户细读 ListBox 时将信息复制到剪贴板。复制的信息与所选项目相关,并且允许他们将其(所述信息)粘贴到其他应用程序中以方便使用。有时我会在某些用户的系统上得到 CLIPBRD_E_CANT_OPEN,但在其他用户的系统上却没有。

虽然我仍然无法修复争用,但我能够创建一些代码来查找导致争用的应用程序。我想至少分享这段代码,希望它对某人有所帮助。我将添加我创建的 using 语句、属性和方法,以找到罪魁祸首的 Process 对象。从Process 项中,您可以获得进程的名称、PID、主窗口标题(如果有的话)和其他可能有用的数据。这是我添加的代码行,没有调用它的代码。 (注意:下面的代码 sn-p 我还有一个花絮要分享):

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 Class)因为后者有一个 4 参数 SetDataObject() 方法,其中包括重试计数和以毫秒为单位的重试延迟。这至少从我的代码中删除了一些重试 noise

您的里程可能会有所不同...另外可能会有副作用,我还没有偶然发现,所以如果有人知道它们,请发表评论。无论如何,我希望这对某人有用。

【讨论】:

  • +1 用于使用 System.Windows.Forms.Clipboard 而不是 System.Windows.Clipboard。谢谢
【解决方案4】:

自从我安装了 TeraCopy(Windows 7,64 位)后,我在 WPF 4.0 和 4.5 中也遇到了这个问题。每个 Clipboard.SetText() 都因 System.Runtime.InteropServices.COMException 而失败。

我的第一个解决方案是卸载 TeraCopy - 它有效,但我喜欢这个应用程序,所以我不得不寻找另一个解决方案来解决这个问题。解决办法是更换

Clipboard.SetText("my string");

Clipboard.SetDataObject("my string");

【讨论】:

  • 缺点是,应用程序退出时,系统剪贴板中的数据会被删除。
  • 如果您将第二个参数设置为 true 以在应用程序退出后保留数据,则不会。看看这个:docs.microsoft.com/de-de/dotnet/api/…
【解决方案5】:

RichTextBox 也有同样的问题。以下代码随机崩溃:

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

似乎更喜欢使用 System.Windows.Controls.RichTextBox.Copy

【讨论】:

    【解决方案6】:

    我在使用 .NET 4.6.1 从剪贴板检索 XAML 数据时遇到问题。

    错误信息:

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

    【讨论】:

      【解决方案7】:

      我终于找到了使用 DataGrid 实现的默认复制模式的解决方案。

      以前的答案对我不起作用:

      • 使用 Clipboard.SetDataObject(data);而不是 Clipboard.SetText(data) --> 这个解决方案不是我所期望的,我不想自己实现复制功能。
      • 处理 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();
          }
      }
      

      之后,问题就解决了。

      【讨论】:

      • 这对我有用。但是我没有使用“System.Windows.Forms.Clipboard.Clear()”,而是使用了“System.Windows.Clipboard.Clear()”作为 wpf。
      【解决方案8】:

      我在将 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 次或更多,就会发生异常。我测试它的最大计数器是一次性循环剪贴板工作。

      【讨论】:

        【解决方案9】:

        代码 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();
                    }
                }
            }
        }
        

        我已经处理了这段代码。

        【讨论】:

        • 请以比目前更好的方式编辑您的答案。
        【解决方案10】:

        有一个用于此目的的 DataGrid 事件/方法签名 CopyingRowClipboardContent(object sender, DataGridRowClipboardEventArgs e) 并且比 Clipboard.SetDataObject(data)Clipboard.SetText(data) 更可靠。

        这里是如何使用它。

        在名为 myDataGrid 的 dataGrid 的 SelectionUnit 模式下设置“FullRow”

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

        【讨论】:

          【解决方案11】:

          我写了一个WPF Datagrid Export to 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"
          

          为您的数据网格。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-02-08
            • 2013-10-17
            • 1970-01-01
            • 2010-09-23
            • 2011-10-19
            • 1970-01-01
            • 1970-01-01
            • 2014-07-30
            相关资源
            最近更新 更多