2

I am trying to create a C# WinForms clipboard manager that can remember the last X clipboard entries, if it contains either an image or text (not file, streams or other fancy stuff). Then I want to be able to restore the clipboard later, so I can paste the original text or image. My problem is that even if I copy the clipboard as an IDataObject then I am not able to restore it afterwards - at least not for all applications?

To test it out the I created this very simple code:

// Get data object from clipboard 
IDataObject clipboardObject = Clipboard.GetDataObject();

// Set the exact same data object to the clipboard
Clipboard.SetDataObject(clipboardObject);

I have lined up two test cases for this - one Wordpad file and one Word file. Both with a simple formatted text - e.g. color coded or italic - something so it is possible to see it is not clear-text only.

If I copy the text from Wordpad, before launching my simple application then I can paste that text again just fine with formatting - no problem.

Doing the same in Word then it pastes something weird - an empty file object or alike!?

Weird empty object inserted in to Word

In the bottom left corner in Word it writes:

Double-click or double-tab to Open Microsoft Word Document

It seems like it now see the clipboard as a file-pointer or alike? If I double-click the area then it actually does open up a new Word file!? I don't get it?

I have already checked out C# Backing Up And Restoring Clipboard, who actually is identical, I can admit, but I have also tried the various proposals in there and nothing works for me at least? I hope that after 10 years there might be better answers? ;-)

Restoring only text formats from the object doesn't work either as the clipboard will act if it is empty then (it doesn't paste anything).

Does anyone have any experience with restoring the clipboard (in terms of texts and images)?

My goal is to have the clipboard history saved in e.g. a Dictionary and then traverse up/down, so I can restore the one I need.


SOLUTION

Based on @Jimi's comment below then this did solve my problem:

// Set the exact same data object to the clipboard
Clipboard.SetDataObject(clipboardObject, true);

As I understand it then it actually will refer pointers, if the copy parameter is not set or set to false. If this parameter is set to true then it will set the actual data in the clipboard and the data will stay there even after my application exists.


UPDATE - how to save/restore objects that has been put to clipboard with SetText

Though I have achieved the primary question for this then I still have a challenge and I think it can be covered here also. I want to store the clipboard objects, so I can fetch them later - probably in a Dictionary. I have then created this PoC and it does seem to work in many cases but if the data is being stored to the clipboard as SetText then it seems to return an empty clipboard when restoring and pasting that?

If you view this code here then I am restoring the clipboard that I have manually set - it will not paste anything and the clipboard seems empty:

        // Define variables
        Dictionary<int, object> clipboardObjects = new Dictionary<int, object>();
        IDataObject clipboardObject;

        // Get formatted text content from clipboard (copied from Word)
        clipboardObject = Clipboard.GetDataObject();

        // Add clipboard object in the first [0] dictionary
        clipboardObjects.Add(0, clipboardObject);

        // Put new content to the clipboard - just to show we can destroy it before restoring again
        Clipboard.SetText("Test, to alter the clipboard");

        // Add clipboard object in the second [1] dictionary
        clipboardObject = Clipboard.GetDataObject();
        clipboardObjects.Add(1, clipboardObject);

        // Now restore the second [1] clipboard object - which will result in an empty clipboard
        clipboardObject = (IDataObject)clipboardObjects[1];
        Clipboard.SetDataObject(clipboardObject, true);

The above will result in a blank clipboard - or at least it does not paste anything. The funny part is that if I restore the first [0] object (which contains formatted text) then it will restore that and I can paste this fine!?

Why can't it restore the SetText object?

My problem with this not working is that I cannot know how applications store the data to the clipboard. SetText could be the way they store it. I have tested this with SetDataObject instead of SetText and then I can restore it.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Beauvais
  • 2,149
  • 4
  • 28
  • 63
  • 2
    The call to `SetDataObject(IDataObject)` is actually a call to [OleSetClipboard()](https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard). When you use this overload, you specify `copy = false`, which implies that [OleFlushClipboard()](https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-oleflushclipboard) is not called (read the Remarks section here). This will set a pointer to the IDatataObject which is then rendered *later* (deferred). It's usually done when you have a lot of data to set to the Clipboard. You app should render the data when requested. – Jimi Dec 06 '20 at 00:48
  • 1
    @Jimi - please create an answer which I will accept. I have updated my question in terms of restoring from a `Dictionary` and with data from `SetText` and if you by any chance can give some bonus on how to do that then that would be much appreciated but not required for accepting the answer. – Beauvais Dec 06 '20 at 11:31
  • 1
    To set the text, write something like: `var data = new DataObject(); data.SetData(DataFormats.UnicodeText, "Test, to alter the clipboard"); Clipboard.SetDataObject(data, false);` -- Sorry, I don't think I'm going to post an answer to this question, I don't want t give *false hope* to possible future readers. To restore an IDataObject / IComDataObject and handle the advise / unadvise sinks plus all the possible variants of OLE + COM and non-COM type serialization and the nuances related to what each application expects to see on the Clipboard (+ the ownership thing), you need to write a book. – Jimi Dec 07 '20 at 14:12
  • 1
    You really cannot use the .Net provided Clipboard object to do this. You have to go *under* and handle `FORMATETC` and `STGMEDIUM` yourself (plus the serialization procedure). – Jimi Dec 07 '20 at 14:15

1 Answers1

1

As no one did answer this, then I will do it myself as I did figure out a solution based on @Jimi's comment. My answer does not cover 100% of my question(s) but it solves my problem. I doubt it will work for everything but at least it works for my basic usage of it, which focuses only on texts and images from the clipboard.

I have tested the below code in Windows 10 with Wordpad and Word and it works but of course things can change or work differently in your scenario but maybe it can give some hints for someone with a similar problem :-)

The code will grap relevant formats from the clipboard (formatted texts or images) and save it to a List of Dictionary. Afterwards you can do whatever you want with the clipboard and later you can retrive and restore the clipboard again.

using System.Windows.Forms;
using System.Collections.Generic;

// Define variables
int key = 0;
IDataObject clipboardObject;
SortedList<int, Dictionary<string, object>> clipboardOriginals = new SortedList<int, Dictionary<string, object>>(); // can be referred by integer and can contain clipboard objects

// ---------
// Save formatted clipboard to a list of dictionaries

// Get the clipboard object
clipboardObject = Clipboard.GetDataObject();

// Walk through all (relevant) clipboard formats and save them in a new object
var formats = clipboardObject.GetFormats(false);
Dictionary<string, object> clipboardFormats = new Dictionary<string, object>();
foreach (var format in formats)
{
    if (
        format.Contains("Text") ||
        format.Contains("Hyperlink") ||
        format.Contains("Bitmap")
    )
    {
        // Add the clipboard format to the clipboard object
        clipboardFormats.Add(format, clipboardObject.GetData(format));
    }
}

// Save the clipboard formats to the dictionary containing all originals
clipboardOriginals.Add(key, clipboardFormats);

// ---------
// Now do whatever you want with the clipboard
Clipboard.Clear();
Clipboard.SetText("Cleared");

// ---------
// Restore the (semi) original clipboard - at least the relevant formats we have saved

DataObject data = new DataObject();
foreach (KeyValuePair<string, object> kvp in clipboardOriginals[key])
{
    if (kvp.Value != null)
    {
        data.SetData(kvp.Key, kvp.Value);
    }
}

// Copy the saved formats to the clipboard
Clipboard.SetDataObject(data, true);

// Try now to paste the clipboard - it should contain the original formatting (hopefully)? :-)

This may not be bullet-proof at all but so far it seems to work for me and my usage.

Beauvais
  • 2,149
  • 4
  • 28
  • 63