[VB6] Using IShellWindows to register for SHOpenFolderAndSelectItems
Shell Window Registration V2
People who've made Explorer replacements or other apps that display a window have found that they can, at best, get the folder passed to them when someone uses 'Show in folder'/'Show in Explorer' in apps like Firefox, Chrome, and torrent clients. However, if you register everything properly, any app calling SHOpenFolderAndSelectItems will be able to pass the names of selected files to a folder you've registered as displaying.
Note that for it to start your app if it's not running with the path in question registered, it will have to be the default that opens when you pass a folder to ShellExecuteEx (i.e. registered as an Explorer replacement on the system).
After Windows XP the API changed. I've completed this demo for Windows 7-10, which uses the SelectItem in IShellView. Windows XP uses IShellFolderViewDual instead.
Requirements oleexp.tlb v4.81 (Released with this demo on 2022 Jan 19)
oleexpimp.tlb v2.11 (Updated with the update to this demo)
oleexp Add-ons mIID.bas and mPKEY.bas (Included in the oleexp download, be sure to use the one from 4.8)
The question of how to do this has been asked many times in various forums for various languages, but apart from alluding to the need to use IShellWindows.RegisterPending, nobody definitely answered.
Special thanks to The_trick for identifying the problem with MSDN's IShellWindows documentation... RegisterPending absolutely does not accept a VT_VARIANT|VT_BYREF variant, but will accept any number of types of variants that can be converted to a pidl. This demo uses a simple BSTR (VB's String type), and The_trick notes it also accepts a CSIDLs, IPersistIDList, IPersistFolder2, IStream, and Byte Arrays (perhaps containing a full ITEMIDLIST structure rather than a pointer to one?). More work is needed to identify the best way to work with this.
The key is using both RegisterPending and Register, since RegisterPending accepts a path but not an object implementing all the required interfaces, and Register takes the object but not a path (and it never tries looking anything up). It uses APIs to convert the ThreadID to an hWnd to associate them-- you'll find that the cookie RegisterPending returns will be the same as the one RegisterReturns, even if you don't use the same variable like my code; this shows that, unlike MSDN indicates, this calls are two parts of the same whole.
In a full app, you'd call RegisterPending, create the file display, then finalize with Register.
Code:
Set pSW = New ShellWindows
Set pDisp = ucSW1.object
pSW.RegisterPending App.ThreadID, Text1.Text, VarPtr(vre), SWC_BROWSER, lCookie
pSW.Register pDisp, Form1.hWnd, SWC_BROWSER, lCookie
In the future I plan on releasing a more complete implementation of these various folder interfaces that work with my Shell Browser control, to create a full replacement for Explorer.
As a bonus, the UserControl has full prototypes for several additional interfaces that weren't used.
Notes
-RegisterPending and Register are linked... two parts of the same whole both needed for a shell window. The way it wprks, your app can register multiple locations with RegisterPending, but each one must have a unique hWnd associated with it... this is because when Register is called to finalize it, it looks up the thread with GetWindowThreadProcessId to complete the information.
-If you do register multiple windows, you must complete them one at a time (RegisterPending, load, Register)... because each Register call will take the most recent matching thread ID because it steps backwards through the structures that have been added.
-Whenever you're closing out a folder, call IShellWindows.Revoke with the cookie (keep track of them per-window). Register will return the same cookie RegisterPending did; if these get mismatched, it would be because you called RegisterPending multiple times before Register, and represent a problem.
-It's been established that only Windows XP uses IShellFolderViewDual.SelectItem... Windows 7-10 use IShellView. I wanted to solve it for XP purely for the challenge though. Getting the pidl was very, very complicated, as it was stored inside a SAFEARRAY inside a VT_ARRAY|VT_UI4 variant. It took some doing but I worked it out:
Code:
Dim pidlItem As Long
Dim sa1 As SAFEARRAY1D
Dim pSA As Long
CopyMemory ByVal VarPtr(pSA), ByVal (VarPtr(pvfi) + 8&), 4& 'Offset 8 is where the data begins. Here, a pointer to the SAFEARRAY
CopyMemory ByVal VarPtr(sa1), ByVal pSA, LenB(sa1) 'We now have the full SAFEARRAY struct...
pidlItem = sa1.pvData '...and pvData is our pidl.
With that completed that functionality is now available on Windows XP, however the parts of the demo responding to the IShellWindows enumeration demo use IShellItem APIs not present on XP that you'll need to replace.
-It's been established that only Windows XP uses IShellFolderViewDual.SelectItem... Windows 7-10 use IShellView. I wanted to solve it for XP purely for the challenge though. Getting the pidl was very, very complicated, as it was stored inside a SAFEARRAY inside a VT_ARRAY|VT_UI4 variant. It took some doing but I worked it out:
Code:
Dim pidlItem As Long
Dim sa1 As SAFEARRAY1D
Dim pSA As Long
CopyMemory ByVal VarPtr(pSA), ByVal (VarPtr(pvfi) + 8&), 4& 'Offset 8 is where the data begins. Here, a pointer to the SAFEARRAY
CopyMemory ByVal VarPtr(sa1), ByVal pSA, LenB(sa1) 'We now have the full SAFEARRAY struct...
pidlItem = sa1.pvData '...and pvData is our pidl.
With that completed that functionality is now available on Windows XP, however the parts of the demo responding to the IShellWindows enumeration demo use IShellItem APIs not present on XP that you'll need to replace.
Re: [VB6] Using IShellWindows to register for SHOpenFolderAndSelectItems
Example: "How to select item in already opened folder (and deselect others)"
Code:
Dim k As Long
Dim nCount As Long
Dim shItem As IShellItem2
'get number of elements
Call spfv.ItemCount(SVGIO_ALLVIEW, nCount)
For k = 0 To nCount - 1
spfv.GetItem k, IID_IShellItem, shItem
'retrieve the name of item
shItem.GetDisplayName SIGDN_PARENTRELATIVEPARSING, lpPath 'or SIGDN_DESKTOPABSOLUTEPARSING (for full path)
sPath = LPWSTRtoStr(lpPath)
Debug.Print sPath
If sPath = "xxxxx" Then ' checking that we obtained the desired path
'select it, and remove both "selection" and "focus rectangle" from other items
Call spfv.SelectItem(k, SVSI_DESELECTOTHERS)
Call spfv.SelectItem(k, SVSI_FOCUSED)
Call spfv.SelectItem(k, SVSI_SELECT)
Exit For
End If
Next
Re: [VB6] Using IShellWindows to register for SHOpenFolderAndSelectItems
Hi,
I am trying to use this in C# project but it is so different from VB6 and I am trying to understand what is supposed to happen when user clicks "Show in folder" in Chrome. It looks like I've registered it correctly because I get cookie and Explorer doesn't open for that folder (Chrome file download there), but nothing happens. It looks like somehow "IServiceProvider_QueryService" from ucSWctl is called but I can't connect the dots how and why this is called. Your pDisp is I believe an IDispatch class and I expected Invoke would be called once user clicks in Chrome but it is not
Re: [VB6] Using IShellWindows to register for SHOpenFolderAndSelectItems
IServiceProvider_QueryService basically just points back to the class that implements all of those interfaces, to tell Windows 'yes I implement that'.
You'll need a class that implements everything; I assume you've defined the interfaces, so to implement them you'd need
class ucSW : IServiceProvider, IWebBrowserApp, etc
{
void IServiceProvider.QueryService
and so on. Though I honestly don't know the details of how interfaces work in C#, whether you'd have to implement IUnknown and IDispatch manually-- in VB6, they're automatically implemented behind the scenes.
Re: [VB6] Using IShellWindows to register for SHOpenFolderAndSelectItems
Note: There was an issue with oleexp.tlb 5.01/oleexpimp.tlb 2.11 with this project (and likely others), if you're getting Uexpected error (32810), upgrade to the latest version, at least 5.03/2.12.