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

88

我有一个使用DataGrid的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()

这真的很烦人。

我看到一些关于这个问题的参考资料在这里以及网络上的各个地方,但没有真正的解决方法。

我可以确认,在Visual Studio中出现此异常时,剪贴板被锁定,因为我无法复制粘贴消息(必须写入文件)。 而且,在复制过程开始之前,剪贴板并未被锁定。

如何解决这个问题?


当您尝试在启动IDE时看到最近打开的项目时,实际上问题会在Visual Studio 2019中显现出来,此时您尝试复制sln文件。 - JGFMK
12个回答

117

我们正在使用.NET 4.0。我们曾经遇到过相同的问题,但是在注销系统后,代码在一段时间内可以正常工作。

最终,我们找到了替代方案。

如果您想将字符串复制到剪贴板中,

string data = "Copy This"

到目前为止,我一直在使用以下方法

Clipboard.SetText(data);

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

Clipboard.SetDataObject(data);

而且它有效 :). 我再也没有遇到这个问题了。


5
很好。我使用.NET 4.5,问题仍然存在。似乎当我重新选择ListView中的一个项目时,就会出现这个错误,但我不知道为什么。 - Alois Kraus
3
我使用WPF和.NET 4.5(而不是4.5.1),这个解决方案让问题消失了! :-) - Marcel
2
SetDataObject在这里运行良好。SetText会抛出异常,但另一个不会。谢谢! - Fabiano
我发现这个调用在多个数据网格剪贴板复制中运行良好。 - sailfish009

85

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

5
微软已在.NET 4.5中修复了这个问题,现在的代码与Windows Forms组件中的代码类似。剪贴板程序现在具有重试计数和延迟时间,而不是一旦无法访问剪贴板就立即失败。 - Alex Wiese
2
我正在使用.NET 4.0并遇到相同的问题。我不想触碰App.xaml文件。这个问题有其他解决方案吗??? - kushdilip
6
@kushdilip,当你控制设置剪贴板数据的方法时,那个解决方案是可行的,但当该方法不在_你的_代码中时,它并没有什么帮助。例如WPF控件和第三方控件。如果你没有在你的代码中实现bug修复,那么在.NET 4.0上你仍然会受到影响。 - Alex Wiese
7
SetText在4.5中并非固定不变,但使用SetDataObject似乎可以解决它 - 但是:我的程序将文件夹路径复制到剪贴板。它正常工作,但如果我在VS调试器中运行应用程序并打开.NET异常捕获,会发生一些奇怪的事情。如果我粘贴到记事本中,它可以正常工作,但如果我粘贴到Windows资源管理器的位置框中,我的应用程序会抛出一个COM异常“Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC))”。只有在启用异常捕获的调试过程中才会发生这种情况,因此该异常必须被某个位置捕获和处理,但这仍然很奇怪。 - Dave
4
即使在 .net 4.7.1 中,即使使用 SetDataObject,我也可以通过快速且重复点击“复制”按钮来随意触发此异常。我将在所有我的 WPF 应用程序中包括此处理程序作为备用方案(尽管我不喜欢魔法数字,因此我还将包括 const int CLIPBRD_E_CANT_OPEN = unchecked((int) 0x800401D0))。 - dlf
显示剩余2条评论

11

我也遇到了一个问题,在一个应用程序中,当用户浏览ListBox时,我将信息复制到剪贴板中。 被复制的信息与所选项相关,并使他们可以将其(该信息)粘贴到其他应用程序中以方便使用。 偶尔会在某些用户系统上出现CLIPBRD_E_CANT_OPEN错误,但在其他用户系统上却没有出现。

虽然我仍然不能解决这个问题,但我已经创建了一些代码来找到引起争用的应用程序。 我想至少分享这段代码,希望它能有所帮助。 我将添加using语句、属性和方法来查找罪犯的进程对象。 从进程项中,您可以获取进程的名称、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 Class),因为后者有一个包括重试计数和重试延迟(以毫秒为单位)的四个参数的SetDataObject()方法。这至少从我的代码中去除了一些重试噪音

你的里程可能会变化...而且这可能会带来我还没有遇到的副作用,所以如果有人知道它们,请评论告诉我。无论如何,我希望这对某些人有用。


3
+1 使用System.Windows.Forms.Clipboard而不是System.Windows.Clipboard。谢谢 - Dmase05

8

我在使用WPF 4.0和4.5时遇到了同样的问题,因为我安装了TeraCopy(Windows 7 64位)。每次使用Clipboard.SetText()都会失败并抛出System.Runtime.InteropServices.COMException异常。

我的第一个解决方案是卸载TeraCopy - 这样做确实解决了问题,但我很喜欢这个应用程序,所以我必须寻找另一种解决方法。最终的解决方案是替换

Clipboard.SetText("my string");

使用

Clipboard.SetDataObject("my string");

缺点是,当应用程序退出时,数据将从系统剪贴板中删除。 - hillin
2
如果将第二个参数设置为true以在应用程序退出后保留数据,则不会被清除。请查看此链接:https://learn.microsoft.com/de-de/dotnet/api/system.windows.forms.clipboard.setdataobject?view=netframework-4.8#System_Windows_Forms_Clipboard_SetDataObject_System_Object_System_Boolean_ - pr0gg3r

2

我曾经遇到一个问题,无法使用.NET 4.6.1从剪贴板中检索XAML数据。

错误信息如下:

OpenClipboard Failed (Exception from 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 { }
}

2

我也遇到了RichTextBox的同样问题。以下代码会随机崩溃:

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

看起来最好使用 System.Windows.Controls.RichTextBox.Copy


2
我最终找到了一种使用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();
    }
}

之后,问题得到了解决。

最初的回答

这对我有效。但是,针对 WPF,我使用了“System.Windows.Clipboard.Clear()”,而不是“System.Windows.Forms.Clipboard.Clear()”。 - Roberto

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

此外,如果循环次数超过10次或更多,您可以在while中设置一个计数器,否则将引发异常。我测试了它的最大计数器为1,在一次循环中剪贴板工作。

1

代码 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

有一个DataGrid事件/方法签名可以实现这个目的:CopyingRowClipboardContent(object sender, DataGridRowClipboardEventArgs e),比Clipboard.SetDataObject(data)Clipboard.SetText(data)更可靠。

以下是如何使用它。

将数据网格myDataGrid的SelectionUnit模式设置为“FullRow”。

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

我们有一个方法叫做myDataGrid_CopyingRowClipboardContent,它会被调用每一行数据网格中的内容都会被复制到剪贴板。例如,对于一个有七行的数据网格,这个方法会被调用七次。
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接