[VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
IExplorerBrowser
IExplorerBrowser is an easy to use, more complete version of IShellView (in fact, it has an IShellView at its core that you can access) that lets you have a complete Explorer frame on your form, with very little code. You can either have just a plain file view, or with a navigation tree and toolbar. It uses all the same settings and does all the same things as Explorer, and your program can interact with those actions to do things like browse for files, or be the basis of a namespace extension.
The only complication is that there's no event notifying of an individual file selection within the view, and getting a list of selected files is fairly complex- however there is a function to do it in the demo project.
Here's how it looks if you're just using folder view without the frames:
INamespaceTreeControl
If all you want is the navigation tree, you have the INamespaceTreeControl. It's got a decent amount of options for however you want to display things, including checkboxes. There is a wide range of events that you're notified of via the event sink, and most of these use IShellItem- the demo project does show to to convert that into a path, but it's a very useful interface to learn if you're going to be doing shell programming. The selection is reported through IShellItemArray, which is slightly easier than IDataObject.
It's got one little quirk though... you have the option to set the folder icons yourself, but if you don't want to do that and just use the default icon that you see in Explorer, you have to return -1, which requires a v-table swap. The demo project shows how to go both ways, no thanks to MSDN and their complete lack of documentation of this.
But this is by far the easiest to create way of having a full-featured Explorer-like navigation- I've made a regular TreeView into this, and it took hundreds of lines and heavy subclassing. This is a simple object. (Note that it does support some advanced features through related interfaces, like custom draw, drop handling, and accessibility... these interfaces are included in oleexp, but have not been brought to the sample project here, perhaps in the future I'll do a more in-depth one if there's any interest)
Requirements
Windows Vista or higher required as these interfaces did not exist in earlier OS versions oleexp.tlb: Modern Interfaces Type Library v4.0 or higher (17 Jun 2015) - Only required in the IDE.
mIID.bas - oleexp add-on module, included in the oleexp download
These 'controls' create themselves- all you need is a blank form, and here's the creation code for a basic idea of how these things work (code to initialize some variables omitted):
Code:
Set pNST = New NamespaceTreeControl
pNST.Initialize Me.hWnd, prc, lFlag
Set pAdv = New cNSTEvents
Set pUnkAdv = pAdv
pNST.TreeAdvise pUnkAdv, lpck
pNST.InsertRoot 0, isiDesk, SHCONTF_FOLDERS, NSTCRS_EXPANDED Or NSTCRS_VISIBLE, pif
UPDATE: Enumerating selection in virtual location for IExplorerBrowser.
So I was showing how this demo can be used to browse FTP sites and realized the IDataObject/DragQueryFile method of obtaining the selection fails in virtual folders that don't have local paths. This is an alternative method that will get their IShellItems, display names, and (if applicable and including network) paths, so that you can further interact with them (for instance, to copy to/from FTP, you can pass the IShellItems to IFileOperation even if there's no valid path):
Code:
'frmBasic
Private Sub Command1_Click()
Dim hr As Long
Dim sOut As String
Dim pIDO As oleexp.IDataObject
Dim psv As IShellView
Dim fmt As FORMATETC
Dim stg As STGMEDIUM
Dim hDrop As Long
Dim uNumFiles As Long
Dim i As Long
Dim Filename As String
pEBrowse.GetCurrentView IID_IShellView, psv
If (psv Is Nothing) Then
Debug.Print "Failed to created IShellView"
Exit Sub
End If
Dim psia As IShellItemArray
Dim penm As IEnumShellItems
Dim lpName As Long
Dim psiChild As IShellItem
psv.GetItemObject SVGIO_SELECTION, IID_IShellItemArray, psia
If (psia Is Nothing) = False Then
psia.EnumItems penm
If (penm Is Nothing) = False Then
Do While (penm.Next(1&, psiChild, i) = NOERROR)
psiChild.GetDisplayName SIGDN_NORMALDISPLAY, lpName
Filename = BStrFromLPWStr(lpName)
sOut = sOut & "Display: " & Filename
psiChild.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpName
Filename = BStrFromLPWStr(lpName)
sOut = sOut & " | AbsParse=" & Filename & vbCrLf
Loop
Else
Debug.Print "Failed to enumerate selection."
End If
Else
Debug.Print "Failed to get selection array."
End If
'old way that only works on non-virtual locations:
'psv.GetItemObject SVGIO_SELECTION, IID_IDataObject, pIDO
'If (pIDO Is Nothing) Then
' Debug.Print "Failed to create IDataObject"
' Exit Sub
'End If
'
'
'fmt.cfFormat = CF_HDROP
'fmt.dwAspect = DVASPECT_CONTENT
'fmt.lindex = -1
'fmt.TYMED = TYMED_HGLOBAL
'stg.TYMED = TYMED_HGLOBAL
'
'hr = pIDO.GetData(fmt, stg)
'Debug.Print "GetData hr=" & hr
'
'hDrop = GlobalLock(stg.Data)
'
'uNumFiles = DragQueryFile(hDrop, &HFFFFFFFF, "", 0)
'Debug.Print "got nfiles=" & uNumFiles
'For i = 0 To (uNumFiles - 1)
' Filename = String$(260, 0)
' Call DragQueryFile(hDrop, i, Filename, Len(Filename))
' If (InStr(Filename, vbNullChar)) > 1 Then
' Filename = Left$(Filename, InStr(Filename, vbNullChar) - 1)
' Debug.Print "filename=" & Filename
' sOut = sOut & Filename & ", "
' End If
'Next i
'Call GlobalUnlock(stg.Data)
'ReleaseStgMedium VarPtr(stg)
Debug.Print "output=" & sOut
Text1.Text = sOut
End Sub
It's basically the same way it's done on the INamespaceTreeControl form, only difference being that you still need to go through IShellView.
UPDATE: Showing a Custom List of Files
(8/2018) This project allows you to browse to any particular location on the system, but what if you want to do something like display a Search Results folder, or some other view where all the files reside in different locations? There's an interface that allows you to easily do just that. See the new project: [VB6] Display search results or other custom file set in IExplorerBrowser
Last edited by fafalone; Aug 24th, 2018 at 12:10 AM.
Reason: New related project details added
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
Here's what the most complicated part for both, selecting items, looks like:
INamespaceTreeControl
Code:
Dim pisia As IShellItemArray
Dim lpName As Long, sName As String
Dim iesi As IEnumShellItems
Dim psi As IShellItem
pNST.GetSelectedItems pisia
pisia.EnumItems iesi
Do While (iesi.Next(1, psi, 0) = NOERROR)
psi.GetDisplayName SIGDN_FILESYSPATH, lpName
sName = sName & BStrFromLPWStr(lpName) & ", "
Set psi = Nothing
Loop
Debug.Print "SelectedItems=" & sName
Text1.Text = sName
Set iesi = Nothing
IExplorerBrowser
Code:
Dim hr As Long
Dim sOut As String
Dim pIDO As oleexp.IDataObject
Dim psv As IShellView
Dim fmt As FORMATETC
Dim stg As STGMEDIUM
Dim hDrop As Long
Dim uNumFiles As Long
Dim i As Long
Dim Filename As String
pEBrowse.GetCurrentView IID_IShellView, psv
If (psv Is Nothing) Then
Debug.Print "Failed to created IShellView"
Exit Sub
End If
psv.GetItemObject SVGIO_SELECTION, IID_IDataObject, pIDO
If (pIDO Is Nothing) Then
Debug.Print "Failed to create IDataObject"
Exit Sub
End If
fmt.cfFormat = CF_HDROP
fmt.dwAspect = DVASPECT_CONTENT
fmt.lindex = -1
fmt.TYMED = TYMED_HGLOBAL
stg.TYMED = TYMED_HGLOBAL
hr = pIDO.GetData(fmt, stg)
Debug.Print "GetData hr=" & hr
hDrop = GlobalLock(stg.Data)
uNumFiles = DragQueryFile(hDrop, &HFFFFFFFF, "", 0)
Debug.Print "got nfiles=" & uNumFiles
For i = 0 To (uNumFiles - 1)
Filename = String$(260, 0)
Call DragQueryFile(hDrop, i, Filename, Len(Filename))
If (InStr(Filename, vbNullChar)) > 1 Then
Filename = Left$(Filename, InStr(Filename, vbNullChar) - 1)
Debug.Print "filename=" & Filename
sOut = sOut & Filename & ", "
End If
Next i
Debug.Print "output=" & sOut
Text1.Text = sOut
Call GlobalUnlock(stg.Data)
ReleaseStgMedium VarPtr(stg)
Last edited by fafalone; Nov 24th, 2016 at 05:52 PM.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
The good news is that error was caused by explicitly having "olelib.IUnknown", which was easily fixed so that error won't happen again.
The bad news is that the OnBefore/AfterContextMenu function where that was located causes an app crash, and I went back and tested it with the version at the time this was released and it was present there too and I missed it. For reasons I can't even begin to comprehend, it's telling me that "oleexp3.IUnknown" is an unknown type, when it clearly isn't as that interface it forward defined AND appears before the oleexp interfaces, but I don't know that that would correct it either. I'm truly at a loss to explain whats causing the crash right now. It's really supposed to be "As Any" but VB doesn't allow that in functions.
Note: If you do use it the context menu events have their last parameter as IContextMenu3; I'm not going to update the sample project just yet while I look for a solution to the crashing.
Last edited by fafalone; Aug 13th, 2015 at 11:54 PM.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
Sometimes it does, sometimes it doesn't. Should probably verify the crash on other systems anyway since I've noticed I get the right-click crash on any app that hosts this object, not just mine, and not just VB apps. It's a pain because to make a new folder I have to go back into Explorer instead of whatever app I'm saving a file in.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
The issue with right-click crash was finally resolved today; like I said I wasn't sure if anyone else had the problem but if you did, it's caused by a bad context menu shell extension; in my systems case SimpleExt.dll. After disabling that using nirsoft's ShellExView, no more right-click crash in this app or any others.
Only oddity now is that OnAfterContextMenu must be swapped out and return E_NOTIMPL. If it returns 0, theres a crash. If it's not swapped and Err.Raise E_NOTIMPL is used, there's no crash but the context menu never appears.
-----------------------
But the most pressing issue is how do I get that void* ppv ? The riid confirms that it's an IContextMenu object, but the current ppv As IContextMenu doesn't work (==Nothing), so I changed it to ByVal ppv As Long, and it's a non-zero number, but treating it like I've treated every other such pointer, vbaObjSetAddRef IContextMenu, ppv -this results in an app crash.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
What exactly are you trying to do?
You could subclass the hWnd and get those events like you would for any other hwnd, but there's probably a better route to whatever end result you're looking for.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
mIID.bas is included in the main oleexp zip file with the tlb. It can't be referenced by GUID so that leaves hard paths; but it's a common file (it holds all IID_x, FOLDERID_x, SID_x, BHID_x, and some GUID functions, saving a ton of time and code by not having to add on a per-project basis and convert from string to guid) so I don't include it with every project, so that one instance can be easily upgraded.
Sorry I should have noted that in the Requirements section; it has been updated.
Last edited by fafalone; Jul 28th, 2017 at 05:39 PM.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
I am having a problem just compiling your sample. I have v4.5 of oleexpl.tlb in SysWOW64 and I am using the latest version (15) of mIID.bas.
When I compile, I get the error "Procedure declaratino does not match descrition of event or procedure having the same name" and line 121 in the class module cNSTEvents is highlighted:
Code:
Public Sub INameSpaceTreeControlEvents_OnBeforeContextMenu(ByVal psi As IShellItem, riid As UUID, ppv As IContextMenu
Any idea what is going on? All I did was load the .vbp fie and when it couldn't find mIID.bas I told it to continue loading and then I inserted mIID.bas myself. Other than that I haven't done anything except to try to compile.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
Per post #9 I had to change the last argument to a long; just change it to ByVal ppv As Long. OnAfterContextMenu will need the same change. Sorry for not updating the demo; I was just planning on updating this sample anyway since I figured out how to load custom file sets from anywhere on the system.
Last edited by fafalone; Aug 11th, 2018 at 02:10 AM.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
So 8 years later I've finally found the cause and solution to the right-click crash in the tree.
The arguments in the context menu events are for supplying your own IContextMenu instead of the system one, and the system relies on the HRESULT to determine if you've done that-- it doesn't validate that the pointer is non-zero. So if you don't return E_NOTIMPL, and don't supply an IContextMenu, it crashes trying to access a null pointer.
Err.Raise E_NOTIMPL in both should fix it; if not, a v-table swap to return it.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
For VB6:
In a standard module (.bas), add:
Code:
Public Function NSTCE_OnBeforeContextMenu(ByVal this As INameSpaceTreeControlEvents, ByVal psi As oleexp.IShellItem, riid As oleexp.UUID, ppv As Long) As Long
Dim lpsz As Long
psi.GetDisplayName SIGDN_FILESYSPATH, lpsz
frmNST.List1.AddItem "OnBeforeContextMenu " & BStrFromLPWStr(lpsz), 0
NSTCE_OnBeforeContextMenu = E_NOTIMPL
End Function
Public Function NSTCE_OnAfterContextMenu(ByVal this As INameSpaceTreeControlEvents, ByVal psi As oleexp.IShellItem, ByVal pcmIn As oleexp.IContextMenu, riid As oleexp.UUID, ppv As Long) As Long
Dim lpsz As Long
psi.GetDisplayName SIGDN_FILESYSPATH, lpsz
frmNST.List1.AddItem "OnAfterContextMenu " & BStrFromLPWStr(lpsz), 0
NSTCE_OnAfterContextMenu = E_NOTIMPL
End Function
In cNSTEvents.cls, add to module level:
Code:
Private m_pOldBCM As Long
Private m_pOldACM As Long
Add Err.ReturnHResult = E_NOTIMPL to INameSpaceTreeControlEvents_OnAfterContextMenu and INameSpaceTreeControlEvents_OnAfterContextMenu in cNSTEvents.cls.
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
Hi Fafalone,thanks for reply.
I ran the compiled version after inserting your changes and it worked.
Why does everything freeze when launched in the IDE after right-click in the navigation tree?
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
Hi Fafalone
you test version works in IDE.
I checked the differences between your Test version and the modified ExplorerBrowser_Rev2 version.
In the cNSTEvents class,were missing the line of code Err.Raise E_NOTIMPL in the subs INameSpaceTreeControlEvents_OnBeforeContextMenu and in INameSpaceTreeControlEvents_OnAfterContextMenu
Re: [VB6, Vista+] Host Windows Explorer on your form: navigation tree and/or folder
?? the sample project redirects those functions; they never run.
In the original project, you can use Err.Raise E_NOTIMPL instead, that will stop the crashing-- however, the default context menu won't appear. You need to use the redirects in the updated version I posted if you want the normal right-click menu to pop up like in Explorer.