In my understanding currently there are two ways to copy virtual files from a Shell Namespace Extension with the Explorer so that Copy GUI will be shown to the user:
Via
IDataObject
interface:Reading a file is done via
IDataObject::GetData
that should supportCFSTR_FILEDESCRIPTORW
,CFSTR_FILECONTENTS
andCFSTR_SHELLIDLIST
clipboard formats at very minimum. RequestingCFSTR_FILECONTENTS
fromIDataObject::GetData
should create anIStream
that is used to access the data. UI is enabled via settingFD_PROGRESSUI
flag whenCFSTR_FILEDESCRIPTORW
is requested.Via
ITransferSource
interface:Reading a file is done via
ITransferSource::OpenItem
requesting forIShellItemResources
. ThenIShellItemResources
should report{4F74D1CF-680C-4EA3-8020-4BDA6792DA3C}
resource as supported (GUID indicating that there is an IStream for the item). Finally anIStream
is requested via parentShellFolder::BindToObject
for accessing the data. UI is handled by the Explorer itself, it is always shown.
My problem is: these two mechanisms are working just fine separately (as you can see from the screenshots). But once I enable both IDataObject
from IShellFolder::GetUIObjectOf
and ITransferSource
from IShellFolder::CreateViewObject
- the approach via IDataObject
is always used leading to the old copy GUI (as on the first screenshot). I see from the trace logs that ITransferSource
is requested several times, but no actions are performed, it just gets Released and destroyed right away.
So how may I force Explorer to show fancy copy GUI when copying from my Shell Namespace Extension?
A minimal reproducible example may be found here: https://github.com/BilyakA/SO_73938149
While working on Minimal Reproducible example I somehow managed to make it work as expected with both IDataObject
and ITranfserSource
interfaces enabled. It happened after:
- unregistred x64 build SNE example (
regsvr32 /u
) - registred x32 build SNE example (it was not working in x64 explorer, root was not opening)
- unregistred x32
- registred x64 again.
Somehow new copy UI was shown to me when copying the files. But I was not able to reproduce this result constantly after I have unregistred x64 SNE, restarted explorer and registered SNE x64 again.
What I have tried:
- Registred both x32 and x64 SNE - still old GUI
- Removed
Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached
value with my NSE GUID and restarted Explorer afterwards. Still old GUI.
I suspect there is some kind of cache (other than Registry) that keeps track if NSE supports ITransferSource
. And since I have developed and tested without ITransferSource
at the beginning - it is cached that my NSE does not support it even that I have added it later. And somehow registering 32bit reset that cache value.
CodePudding user response:
The current code is doing something like this when asked for an IDataObject:
HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
{
....
if (riid == IID_IDataObject)
{
CDataObject* dataObject = new (std::nothrow) CDataObject(this, cidl, apidl);
return ::SHCreateDataObject(m_pidl, cidl, apidl, dataObject, riid, ppv);
}
...
}
SHCreateDataObject already creates already a full-blown IDataObject
with everything needed from the (optional) input PIDL(s), including all Shell formats for items in the Shell namespace (CFSTR_SHELLIDLIST
, etc.).
Passing an inner object 1) will probably conflict with what the Shell does and 2) requires a good implementation (I've not checked it).
So you can just replace it like this:
HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
{
....
if (riid == IID_IDataObject)
{
return ::SHCreateDataObject(m_pidl, cidl, apidl, NULL, riid, ppv);
}
...
}
In fact, the IDataObject
provided by the Shell is complete (supports SetData
method, etc.) so you should never need to implement it yourself, it's more complex than it seems. You can reuse it as a general purpose IDataObject
(you can pass nulls and 0 for the 4 first arguments).
PS: the Shell also provides the "reverse" method: SHCreateShellItemArrayFromDataObject that gets a list of Shell items from an IDataObject
(if the data object contains the expected clipboard formats).