New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NativeClipboard.SetData related API takes no effect #355
Comments
I just ran this test without error: const string txt = @"“We’ve been here”";
using (var cb = new Clipboard())
cb.SetText(txt, $"<p>{txt}</p>");
using (var cb = new Clipboard())
{
Assert.That(cb.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
Assert.That(cb.GetText(TextDataFormat.Html), Contains.Substring(txt));
} |
FYI: The |
Hi @dahall I ran your code but different from my side. const string txt = @"“We’ve been here”";
using (var cb = new NativeClipboard())
cb.SetText(txt, $"<p>{txt}</p>");
using (var cb = new NativeClipboard())
{
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(cb.GetText(TextDataFormat.UnicodeText) == txt ? "Yes" : "No")}");
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(cb.GetText(TextDataFormat.Html).Contains(txt) ? "Yes" : "No")}");
} I got this:
My test env is in WinAppSdk 1.2, STA thread |
Would you mind testing the following code on your system and also let me know your platform (Any CPU, x64, etc) of the app consuming the library? const string txt = @"“We’ve been here”";
using (var cb = new NativeClipboard())
cb.SetText(txt, TextDataFormat.UnicodeText);
using (var cb = new NativeClipboard())
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(cb.GetText(TextDataFormat.UnicodeText) == txt ? "Yes" : "No")}");
const string html = "<p>Body text</p>";
using (var cb = new NativeClipboard())
cb.SetText(html, TextDataFormat.Html);
using (var cb = new NativeClipboard())
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.Html)}, what we suppose to {html}, they are equal? {(cb.GetText(TextDataFormat.Html) == html ? "Yes" : "No")}"); |
I can't get mine to fail so the request above is to help narrow my testing. |
First System.Diagnostics.Debug.WriteLine shows:
And Second System.Diagnostics.Debug.WriteLine:
|
This is now fixed. It is important to use the first parameter in the The following should now all work with the packages now available on AppVeyor (see homepage): const string txt = @"“We’ve been here”";
using (var cb = new NativeClipboard(true))
cb.SetText(txt, $"<p>{txt}</p>");
using (var cb = new NativeClipboard())
{
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(cb.GetText(TextDataFormat.UnicodeText) == txt ? "Yes" : "No")}");
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(cb.GetText(TextDataFormat.Html).Contains(txt) ? "Yes" : "No")}");
}
using (var cb = new NativeClipboard(true))
cb.SetText(txt, TextDataFormat.UnicodeText);
using (var cb = new NativeClipboard())
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(cb.GetText(TextDataFormat.UnicodeText) == txt ? "Yes" : "No")}");
const string html = "<p>Body text</p>";
using (var cb = new NativeClipboard(true))
cb.SetText(html, TextDataFormat.Html);
using (var cb = new NativeClipboard())
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {cb.GetText(TextDataFormat.Html)}, what we suppose to {html}, they are equal? {(cb.GetText(TextDataFormat.Html) == html ? "Yes" : "No")}"); |
Yes, it works, but failed if I passed the WindowHandle of my application to the .ctor() |
Please try the latest AppVeyor packages. I have used every iteration of the constructor without failure. Let me know if you still have errors. |
@zhuxb711 : I have the libraries tested for the next NuGet package update. I'm just waiting for your confirmation on these NativeClipboard changes. Let me know when you've tested. Thanks! |
Yes I tested it, still not working. If remove the handle of the demo application, then everything works as expected |
That took some work, mostly because I missed a disposal problem. All fixed now, even in your test app! Please confirm. |
I still got this after reinstall 3.4.12 from AppVeyor "ExceptionType: InvalidOperationException, Message: HTML format header cannot be processed." |
I saw you refactor the whole NativeClipboard, could share more information? |
|
I just had a thought of a way to retain some of the structure from before. Give me the afternoon to see if I can get it working. |
After playing with it, I think it is too convoluted. I think I'll stick with the changes as committed. My apologies again for the work this may cause you. |
It seems that your latest changes in NativeClipboard is not included in 3.4.12 |
Which changes? It appears to be complete. |
@dahall Thanks for your work, I tested 3.4.13, SetShellItems and GetShellItemArray works for me now. But there is an issue that this code do not work for me. Please check it, thanks. const string txt = @"“We’ve been here”";
NativeClipboard.SetText(txt, $"<p>{txt}</p>");
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {NativeClipboard.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(NativeClipboard.GetText(TextDataFormat.UnicodeText) == txt ? "Yes" : "No")}"); // Throw "Invalid FORMATETC structure (0x80040064 (DV_E_FORMATETC))" here
System.Diagnostics.Debug.WriteLine($"What stored in clipboard: {NativeClipboard.GetText(TextDataFormat.UnicodeText)}, what we suppose to {txt}, they are equal? {(NativeClipboard.GetText(TextDataFormat.Html).Contains(txt) ? "Yes" : "No")}"); By the way, I want to know should I / how to set my window as the window which opened Clipboard? And should I / how to release the Clipboard so that the other process could use clipboard?. Should I call Flush() to make the clipboard available to others process? |
This is now fixed. Minor oversight in my code.
You no longer claim the clipboard for the window. Each method is atomic. Once the method completes, the clipboard belongs again to the system. |
Please see if 3.4.13 from Appveyor resolves the issue with |
I also just pushed a commit that fixed a bug in NativeClipboard.Clear. |
Hi @dahall , I think it not a good idea to flush the whole IDataObject to system on each SetData(). Some formats such as CFSTR_FILEDESCRIPTORW and CFSTR_FILECONTENTS, we must use them together, but we could not do that on current mechanism. So a better solution for NativeClipboard is allow user SetData multiple times to the IDataObject instance and retrieve what they set before from the same instance, only release it to system if user call Flush(). If release the IDataObject to system in each calls, we could not set multiple kinds of data to Clipboard (for example: SetShellItemArray & SetText to clipboard). Here is a demo for you. |
On the other hands, we might also set this data to indicate what we want to perform in the clipboard. It's necessary for user to set multiple data to clipboard at the same time. //Do copy action
NativeClipboard.SetBinaryData(NativeClipboard.RegisterFormat(Shell32.ShellClipboardFormat.CFSTR_PREFERREDDROPEFFECT), new byte[] { 5, 0, 0, 0 }) |
Another bugs, could not retrieve the FileContents set before. Invalid FORMATETC structure was threw in Line 134 //Throw Invalid FORMATETC structure
var result = NativeClipboard.GetData(Shell32.ShellClipboardFormat.CFSTR_FILECONTENTS, DVASPECT.DVASPECT_CONTENT, Index); Here is the demo. |
See example in the docs for |
But it's almost useless if NativeClipboard do not support SetData multiple times and we still need to operate the IDataObject directly. At this scenario, only NativeClipboard.CreateEmptyIDataObject and NativeClipboard.SetIDataObject is used. Some APIs in NativeClipboard will retrieve the current IDataObject and set new data to it (Combination). But the others just replace the current IDataObject with a new one (Replacement). I think all the API should use Combination mode. Besides if each calls to SetData will release the whole IDataObject to system, then API Flush() is useless. |
You've given me an idea, since this class has been so problematic. I've tried doing what you suggest, but flushing and releasing is inconsistent and problematic. What if I just leave those two methods and have all work done through Maybe something like: using (NativeClipboard cb = new())
{
DROPDESCRIPTION dropDesc = new() { type = DROPIMAGETYPE.DROPIMAGE_COPY, szMessage = "Move this" };
cb.Object.SetData(ShellClipboardFormat.CFSTR_DROPDESCRIPTION, dropDesc);
FILE_ATTRIBUTES_ARRAY faa = new() { cItems = 1, rgdwFileAttributes = new[] { 4U } };
cb.Object.SetData(ShellClipboardFormat.CFSTR_FILE_ATTRIBUTES_ARRAY, faa);
FILEGROUPDESCRIPTOR fgd = new() { cItems = (uint)files.Length, fgd = new FILEDESCRIPTOR[files.Length] };
for (int i = 0; i < files.Length; i++)
{
if (i == 0) { cb.Object.SetData(ShellClipboardFormat.CFSTR_FILENAMEA, files[i]); cb.Object.SetData(ShellClipboardFormat.CFSTR_FILENAMEW, files[i]); }
fgd.fgd[i] = new FileInfo(files[i]);
ShlwApi.SHCreateStreamOnFileEx(fgd.fgd[i].cFileName, STGM.STGM_READ | STGM.STGM_SHARE_DENY_WRITE, 0, false, null, out IStream istream).ThrowIfFailed();
cb.Object.SetData(ShellClipboardFormat.CFSTR_FILECONTENTS, istream, DVASPECT.DVASPECT_CONTENT, i);
}
cb.Object.SetData(ShellClipboardFormat.CFSTR_FILEDESCRIPTORW, fgd);
} |
Maybe leave those two methods and move the rest to the extension method to IDataObject Object = NativeClipboard.CreateEmptyIDataObject();
Object.SetBinaryData();
Object.SetShellItemArray();
NativeClipboard.SetIDataObject(Object); |
Actually, I like yours better. It is more intentional. Going that direction now. |
Here's a problem that exists in this scenario. The internal method that sets shell items actually creates an IDataObject -- it doesn't merely add to one. In that case, you'd have to do something like: IDataObject Object = new ShellItemArray(shellitemlist).ToDataObject();
Object.SetBinarayData();
NativeClipboard.SetIDataObject(Object); Is that a problem? |
I want to know if there is a way to set ShellItems through IDataObject.SetData? If that is possible, everything works fine. If not, maybe provide a new method such as NativeClipboard.CreateIDataObjectFromShellItems instead of an extension method to set the ShellItems. That will force the user to set the ShellItems at the beginning rather than through extension method. |
There is, lots of manual work that has proven to be very error prone. That's why I gave up on it and moved to
I like this approach. It keeps things simple. |
It looks great, thanks for your work. The last suggestion is Design the API like this: if Items.Length == 0, just call NativeClipboard.CreateEmptyIDataObject() It makes the user easier to use the API if they want to set ShellItems (even they passed an empty array) |
Having just adjusted all the unit tests, I will warn you that these changes force lots of refactoring. See unit tests for examples. |
… to set data have been removed due to inconsistencies and design challenges. New model forces all setting and getting to be done through IDataObject and it's methods and extensions. See documentation for class for example. Addresses #355.
Please test latest release and reply with comments or issues. |
@dahall Since we have an extension method called IsFormatAvailable() for IDataObject. Should we remove the same one from NativeClipboard.IsFormatAvailable() ? |
This issue is still exists. May you take a look at it? |
The IDataObject one will look at only what is in the object. The NativeClipboard one looks at what is currently in the clipboard. |
Checking it out now |
So it is curious. If you call this on the IDataObject just after setting it and before pushing it to the clipboard, it works. If you call this on the IDataObject you get from the clipboard after setting it, then it fails. The only explanation is that the clipboard cannot maintain the data format. The only hint I got from reading lots of posts is that the IDataObject returned by SHCreateDataObject does not support multiple streams. That seems incorrect though since it works before pushing to the clipboard. I'm out of ideas. If you can make it work, please post your findings. |
I do not have ideas too... I could read the FILECONTENTS as IStream created by Windows Explorer. |
I hate loose ends, so today I wrote the following and confirmed that something is wrong with OleSetClipboard. HRESULT hr = OleInitialize(NULL);
auto id0 = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
auto id = RegisterClipboardFormat(CFSTR_FILECONTENTS);
IDataObject* pDataObj;
if (SUCCEEDED(hr = SHCreateDataObject(NULL, 0, NULL, NULL, IID_PPV_ARGS(&pDataObj))))
{
FORMATETC fmt0 = { id0, NULL, DVASPECT::DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
FILEDESCRIPTORW fd0 = { FD_FILESIZE };
//fd0.nFileSizeLow = 445;
lstrcpy(fd0.cFileName, L"C:\\Temp\\test.txt");
auto hMem = GlobalAlloc(GMEM_MOVEABLE, sizeof(fd0));
auto ptr = GlobalLock(hMem);
memcpy(ptr, &fd0, sizeof(fd0));
GlobalUnlock(hMem);
STGMEDIUM medium0 = { TYMED_HGLOBAL };
medium0.hGlobal = hMem;
hr = pDataObj->SetData(&fmt0, &medium0, TRUE);
FORMATETC fmt = { id, NULL, DVASPECT::DVASPECT_CONTENT, 0, TYMED_ISTREAM };
STGMEDIUM medium = { TYMED_ISTREAM };
IStream* pStream;
if (SUCCEEDED(hr = SHCreateStreamOnFileW(L"C:\\Temp\\test.txt", STGM_READWRITE, &pStream)))
{
medium.pstm = pStream;
if (SUCCEEDED(hr = pDataObj->SetData(&fmt, &medium, TRUE)))
{
hr = OleSetClipboard(pDataObj);
hr = OleFlushClipboard();
}
pStream->Release();
}
pDataObj->Release();
pDataObj = NULL;
}
if (SUCCEEDED(hr = OleGetClipboard(&pDataObj)))
{
FORMATETC fmt = { id, NULL, DVASPECT::DVASPECT_CONTENT, 0, TYMED_ISTREAM | TYMED_HGLOBAL | TYMED_ISTORAGE };
hr = pDataObj->QueryGetData(&fmt);
std::cout << "HR = " << hr << std::endl; // This returns the same FORMATETC error as C#
}
OleUninitialize(); |
Describe the bug and how to reproduce
Just like the code what I provided below
What code is involved
Expected behavior
Looks like the content did not write down to clipboard
The text was updated successfully, but these errors were encountered: