Displaying Shell Elements in a TreeView
This article demonstrates a very simple way to populate a TreeView with files and or folders using the Shell Automation Object provided in Shell32.DLL. Using this object in combination with a System Image List allows you to easily provide a browsable view of the file system with correct icons for each element.
About the Shell Automation Object
With version 4.71 of Shell32.DLL, released with IE 4.0 and included in Windows 98/2000 and above Microsoft finally included some methods to make the wilfully obscure Shell API a bit easier to use from higher-level languages. The methods are exposed from a TypeLibrary embedded within Shell32.DLL called "Microsoft Shell Controls and Automation", and include methods for browsing the Shell's namespace as well as performing some common Shell tasks such as launching Control Panel Items, opening the Find dialogs and so on.
To give an example of using the API, here is how you can get a list of all items in My Computer:
Dim shl As New Shell Dim drives As Folder Dim driveItem As FolderItem Set drives = shl.NameSpace(ssfDRIVES) For Each driveItem In drives.items Debug.Print driveItem.Name, driveItem.Path If (driveItem.IsFolder) Then ' You can get the sub items using driveItem.Folder End If Next
One thing that is unfortunately missing from this however is a method to get hold of the icon for a given item in the shell.
When using the low-level Shell32 API, the icon for an item in the Shell namespace can always be obtained from it's PIDL, a foolishly named structure that describes a "pointer to an ID List"; an ID list being a structure which effectively represents the path to a folder regardless of whether it is implemented as a physical directory on the file system or whether it is a virtual construct such as "My Computer". In the higher level API the PIDL is not accessible so another route is needed to find the icon. Luckily, for file-system related items this is quite easy to achieve using a System Image List.
Getting the icon for an actual file system item with a normal path is described in the System Image List article, so I won't cover it again here. For items like the Desktop and My Computer, however, you can still retrieve the icon using a path fromt the Shell Automation object because even though these items are virtual, they have another form of path addressing which uses GUIDs to identify an item. This system does not appear to be much documented at MSDN, but here a handful of commonly occuring objects:
These GUID based paths are accepted as if they were filenames by the System Image List, and so you can obtain the correct icons. This means you can create a fairly comprehensive shell object browser using hardly any code.
Populating a Tree With Shell Objects
There are three things you need to be able to do to create a TreeView showing shell objects:
I'll cover these in turn.
Associating a System Image List With the Tree
The easiest way to achieve this is to use the code in the vbAccelerator System Image List class. Here's the code used to set up the ImageList:
Private m_cIml As cVBALSysImageList ... Set m_cIml = New cVBALSysImageList m_cIml.IconSizeX = 16 m_cIml.IconSizeY = 16 m_cIml.Create tvwDirs.ImageList = m_cIml.hIml
With this in place, the icon index for a Tree Node can be obtained from its path using the following code. Note the use of use of the bForceLoadFromDisk to ensure the correct icon is loaded; if you do not specify this then the code assumes the object is of type file and will not work for folders, regardless of whether they are file system or not.
lIconIndex = m_cIml.ItemIndex(item.Path, True)
There are two potential approaches that could be taken to sorting items: either you could collate all of the items into a temporary array and sort that, or, and more simply use the features of the vbAccelerator TreeView control to do the sorting for you. The latest release of the control offers a range of sorting methods, including an event-driven callback method in which you can code any sorting scheme. In this case, however, items are sorted alphabetically and may need to be sorted into folder/item groups. The easiest way to support this is to use the TreeView node ItemData to encode whether the item is a folder or an item and then use the etvwItemDataThenAlphabetic sorting mode built into the control.
The TreeView allows you to configure sorting so it is automatically applied, or it can be manually applied. Automatic sorting is convenient but for large numbers of items does not perform very well because it resorts every time an item is added. If you include files then the Windows\System directory can frequently contain in excess of 3,000 objects, and hence the more performant manual sort is a better option.
Here is a code outline of adding folders and items to a TreeView node and then sorting them:
Set nodes = parentNode.Children For Each itm In items.items If (itm.IsFolder) Then Set nod = nodes.Add(, , , itm.Name, _ m_cIml.ItemIndex(itm.Path, True)) nod.ItemData = 0 nod.Children.Add , , , "Unexpanded" Else Set nod = nodes.Add(, , sKey, itm.Name, _ m_cIml.ItemIndex(itm.Path, True)) nod.ItemData = 1 End If Next parentNode.Sort etvwItemDataThenAlphabetic
Uniquely Identifying the File System Object
The vbAccelerator TreeView uses a key to identify each node. This is a good place to store the path of the object that's being added to the tree; however, the key must be unique and the same path may appear more than once in the tree something needs to be done.
The solution adopted in the same is to prepend a unique ID number to the path and then use that as the key. By using a colon as a separator between the prepended ID and the path it is easy to extract again by finding the first occurrence of a colon in the key using Instr.
Wrapping it up: The Demonstrations
The demonstrations bring these techniques together into two samples: one which shows directories only and another which shows all Shell objects in the same view in a similar manner to the Browse for Folder dialog with files showing. The basic technique is to seed the tree with an inital namespace (in the case of the samples, ssfDRIVES is used, but this could be changed to any other folder or the Desktop using ssfDESKTOP if desired.
Whenever a folder is created, a dummy item is added as a child. Then code in the BeforeExpand event is added to check for the dummy item: if it finds it then the dummy is removed and the contents of the sub folder populated in exactly the same way as for the initial load. Note that the BeforeCollapse or Collapse event could also be coded to remove any non-dummy items if you wanted to reduce the memory needed to show the Tree.
Although not coded here, you can also use the Verbs collection of the FolderItem object to get a list of all the actions that can be performed in a right-click menu for any of the items in the view.
This article demonstrates how to populate a TreeView with Shell namespace objects complete with icons with very little code. Performance is pretty much equal to Explorer, although for very large directories will feel a little slower because the application is single threaded (Explorer populates some directories on a background thread which can appear more responsive). The demonstrations should prove useful for any application which wants a customised folder picker or a Folder view.