如何在C#中备份和恢复系统剪贴板?

15
我会尽我最大的努力详细解释我想要实现的目标。我正在使用C#和IntPtr窗口句柄从我的C#应用程序中对外部应用程序执行CTRL-C复制操作。我必须这样做,因为没有办法使用GET_TEXT直接访问文本内容。然后,我在我的应用程序中使用该复制的文本内容。问题在于我现在已经覆盖了剪贴板。
我想要能够做到以下几点:
1.备份剪贴板中的原始内容,这些内容可能是由除我的应用程序之外的任何应用程序设置的。 2.然后执行复制并将值存储到我的应用程序中。 3.然后恢复剪贴板的原始内容,以便用户仍然可以访问其原始剪贴板数据。
这是我迄今为止尝试过的代码:
private void GetClipboardText()
{

    text = "";

    IDataObject backupClipboad = Clipboard.GetDataObject();

    KeyboardInput input = new KeyboardInput(this);
    input.Copy(dialogHandle); // Performs a CTRL-C (copy) operation

    IDataObject clipboard = Clipboard.GetDataObject(); 
    if (clipboard.GetDataPresent(DataFormats.Text))
    {
        // Retrieves the text from the clipboard
        text = clipboard.GetData(DataFormats.Text) as string;
    }

    if (backupClipboad != null) 
    {
        Clipboard.SetDataObject(backupClipboad, true); // throws exception
    }
}

我正在使用System.Windows.Clipboard而不是System.Windows.Forms.Clipboard。这是因为当我执行CTRL-C时,来自System.Windows.Forms的Clipboard类没有返回任何数据,但系统剪贴板确实有。

我研究了一些低级别的user32调用,如OpenClipboard、EmptyClipboard和CloseClipboard,希望它们会帮助我做到这一点,但到目前为止,当我尝试还原时,我仍然会遇到COM异常。

我想也许这与OpenClipboard参数有关,该参数期望应用程序的IntPtr窗口句柄来控制剪贴板。由于我提到我的应用程序没有GUI,所以这是一个挑战。我不确定在这里传递什么。也许有人可以解释一下?

我是否错误地使用了Clipboard类?有没有清晰的方法获取没有GUI的应用程序的IntPtr窗口句柄?有谁知道更好的备份和还原系统剪贴板的方法吗?


我遇到了相同的错误并找到了这篇文章。但是,只有在调用Clipboard.GetDataObject().SetData(myData)时才会出现此错误。如果我调用Clipboard.SetDataObject(myData),它就可以正常工作。为什么会这样?如果有影响的话,我的应用程序是基于WPF的。 - newman
2个回答

22

尝试这样做是愚蠢的。您无法忠实地恢复剪贴板到其先前状态。可能会存在数十种未呈现数据格式,使用“延迟呈现”,如果您尝试呈现所有格式,将导致源应用程序耗尽资源。这就像走进一家餐厅,说“给我所有的菜”。

假设用户在Excel中选择了500行x100列,并将其复制到剪贴板。 Excel“广告”可以在大约25种不同格式中显示此数据,包括位图。 一旦您将其粘贴为位图,您就会强制Excel将其呈现为位图。 这是50000个单元格,可能是一个大约10,000 x 15,000像素的位图。 您期望用户等待Excel解析它以及其他24种格式吗? 不可行。

此外,您将触发WM_DrawClipboard事件,这将影响其他剪贴板查看器。

放弃吧。


8
程序不应在没有明确用户指令的情况下将数据传输到或从剪贴板中获取。——查尔斯·彼得佐德,《Programming Windows 3.1》,微软出版社,1992年。 - Chris Thornton
1
@gtaborga - 你可以通过处理错误、短暂等待并重新尝试来避免“无法打开剪贴板”的问题。我会说,三次失败就放弃吧。不管怎样,我不明白你关于“使用CTRL-C检索的数据量大约是文本句子长度”的评论。如果你要备份原始剪贴板内容,那么你需要考虑最坏情况下的内存情况。 - Chris Thornton
1
问题不在于如何存储渲染数据。OP只想恢复剪贴板状态。因此,如果Excel“广告”了25种格式,则OP不想存储所有这些格式。他只想存储“广告”,并在短时间操作后恢复它。当然,在OP的操作期间,广告应用程序的状态可能已经发生了变化,因此恢复的“广告”可能不再有效,但这很不可能,因为OP的操作非常快速。 - drwatsoncode
2
@ricovox,“广告”将在剪贴板被OP的应用程序覆盖的瞬间失效。恢复延迟呈现格式的句柄是行不通的。真的不行。 - Chris Thornton
1
很好的答案,但不幸的是。延迟渲染是一个可怕的概念。它使剪贴板片段不再完全可序列化(杀死了一整类有用的应用程序),并且在关于尊重用户的复制请求方面会误导用户。你有多少次按下Ctrl+C以为你的内容已经安全地存储了,然后关闭了应用程序(或者它崩溃了),发现Ctrl+V神秘地没有任何作用。处理的推迟还会在粘贴时添加不直观的延迟。 - rkagerer
显示剩余5条评论

6
您可以将剪贴板的内容保存在字典中,并在需要时进行恢复:
public IDictionary<string, object> GetClipboardData()
{
    var dict = new Dictionary<string, object>();
    var dataObject = Clipboard.GetDataObject();
    foreach(var format in dataObject.GetFormats())
    {
        dict.Add(format, dataObject.GetData(format));
    }
    return dict;
}

public void SetClipboardData(IDictionary<string, object> dict)
{
    var dataObject = Clipboard.GetDataObject();
    foreach(var kvp in dict)
    {
        dataObject.SetData(kvp.Key, kvp.Value);
    }
}

...

var backup = GetClipboardData();
// Do something with the clipboard...
...
SetClipboardData(backup);

我遇到的问题是,无论我执行任何SetData或SetDataObject操作,都会出现COM异常,因为它无法打开剪贴板。我会尝试那种方法,但潜在问题是剪贴板似乎被锁定了。 - gtaborga
1
这是在循环中第一次调用SetData时出现的异常:“无法在冻结的OLE数据对象上设置数据”。 - gtaborga
使用Clipboard.SetData方法时,我收到了这个异常,这是每次尝试进行设置操作时都会出现的异常:OpenClipboard失败(HRESULT异常:0x800401D0 (CLIPBRD_E_CANT_OPEN))。 - gtaborga
3
尝试运行这段代码,然后在Excel中复制大量内容。哦等等,确保保存所有打开的文档。最好先创建一个还原点,以防系统崩溃。使用System Internals的Process Explorer测量Excel使用的RAM和CPU数量,并确保在系统崩溃之前结束Excel和您的应用程序的进程。这将是一次值得的尝试。 - Chris Thornton

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