Click here to Skip to main content
Click here to Skip to main content

An All VB.NET Explorer Tree Control with ImageList Management

By , 17 May 2012
 

VB.NET Explorer Tree Control

Introduction

The ExpTree control is a Windows Explorer-like TreeView control. It displays all proper icons, with overlays as appropriate. All Windows folders, including Virtual Folders like Desktop, My Computer, and History are properly displayed and made available to the containing form. The control is packaged with and uses an optimized image list management class that provides both Small and Large Icon image lists for application use. The control is just the visual aspect of a powerful Class Library(ExpTreeLib) that provides functionality over and above that of a combination of the DirectoryInfo and FileInfo classes. As pictured above, ExpTreeLib can easily be used to create a Windows Explorer-like ListView coupled with ExpTree.

Although .Net's FolderBrowserDialog is a useful substitute in many cases, ExpTree is a true control that may be manipulated like any other control on a Windows Form. It has a well defined interface which provides Selected Node change notification to the Form and allows both Design-time and Run-time manipulation of key aspects of the displayed Tree.

I distribute this code as a Visual Studio 2005 Solution which may be upgraded without error to VS2008 and/or VS2010 by the Visual Studio Upgrade Wizard. It targets Framework 2.0.

Version Overview

There are two supported versions of the overall Class Library ExpTreeLib. Version 2.12 which is described in and downloadable from this article, and Version 3.00. Version 2.12 provides a largely static view of the Windows Shell Namespace, including the File System. Version 2.12 is a enhanced version of the package referred to as the "Rollup" version as discussed in the forum.

Version 3.00, which will be described in a soon to be written article, provides a dynamic view of that Namespace and adds a number of features. It is an enhanced version of the package mentioned in the forum as the "unpublished" version. Both versions have developed a community of users over the years that they have been available. Version 2.12 is useful for those applications which do not require a dynamic view or the additional features of version 3.00 and is easier to understand and use. This article provides the basic documentation for either version.

This version (2.12) provides a current view of TreeNodes whenever they are expanded or selected, or changed via Drag and Drop to/from the control. Changes to the file system made outside of the control are not reflected until the changed node is expanded or selected. This version supports a version of Drag and Drop which is discussed in part 2 of this article, Adding Drag and Drop to an Explorer Tree Control. A fundamental difference between version 2.12 and version 3.00 is how Drag and Drop is implemented and how it is illustrated in that version's Demo Forms.

Relative to Version 2.11, Version 2.12 has changes required by Windows Vista/Window 7, other bug fixes, and additional optimization. Applications which reference large Folders on Remote systems, which gave acceptable performance on XP system could, in Version 2.11, become very slow on Vista/Windows7. Version 2.12 fixes that for most common applications. In some cases, Version 3.00 is required to restore performance.

All previously added features and bug fixes are included. See History for details of this and previous updates.

Intended Audience

I have written the article and the code with an audience of developers in mind. I expect that the audience will look at the code and try it out. I have attempted to keep the comments up to date. I am interested in any constructive comments that may lead to improvements in the library.

Background

My design goals were to create a control that only needed one .dll (no auxiliary wrapper .dlls), showed the correct icons on any Windows system, would work with Virtual Folders as well as FileSystem folders, was quick, and used few resources. Since the rest of my code was to be in VB.NET, I wanted the control to be written in VB.NET. I could not find any code on this or any other site that met my requirements. Almost all other similar controls were written in C#, and none fully met the other requirements.

Controls based on DirectoryInfo and FileInfo classes will not handle Virtual Folders. Controls based on adding a reference to Shell32.dll require an extra .dll to wrap the COM interface and will not report hidden files and directories. Applications using either approach require additional classes to deal with icons since neither gets icon information. Since I had written Shell-accessing .dlls in C, I was familiar with the techniques, so I decided to attack the problem using the IShell Folder Interface with SHGetFileInfo providing the icon information.

Class Overview

The control, ExpTree, is packaged with several supporting classes into one library assembly and .dll (ExpTreeLib). ExpTreeLib contains these classes:

ShellDll API declarations, interfaces, structures, enumerations, and constants.
CShItem The main class of this library.
SystemImageListManager A class to manage Large and Small System image lists.
ExpTree The actual control.

Use of the SystemImageListManager class is optional, however, ExpTree initializes and uses it. If the application needs to display FileSystem icons, the class will provide them. See the SystemImageList Manager Class section below, for details.

Details of the CShItem class are discussed below. It wraps a collection of information that describes one folder or file. In use, it is similar to a DirectoryInfo or FileInfo instance. However, it is built using the Shell's IShellFolder interface, and therefore, can represent all folder and file types available on the system.

The library also contains other classes solely for Drag and Drop support which will not be discussed here.

Using the Control

To use the control, add a reference to its .dll to your project, and then add the control to the ToolBox. To add the control to the ToolBox, right click on the ToolBox, click Customize ToolBox, and then browse for the DLL. Once you have done this, you may use it like any other control. In addition to the normal UserControl properties, the ExpTree control exposes several Properties:

AllowDrop Design and Run Time Allow/Disallow Drop on Tree
ShowHiddenFolders Design and Run Time Show/Hide hidden folders
ShowRootLines Design and Run Time Allow/Disallow collapse of TreeRoot
StartupDirectory Design and Run Time Select root directory of Tree
RootItem Run-Time only Set root to a specific CShItem
SelectedItem Run-Time only Gets the currently selected CShItem

StartupDirectory sets the root of the TreeView. It will only accept a SystemFolder as a startup directory. The most useful ones are Desktop and My Computer. Change the StartUpDirectory at design time to see, in the IDE, what the initial display will be.

RootItem is a run-time only property which is used to reset the tree root to another folder which may be any folder available in the TreeView.

Hint

To set an ExpTree to appear to start Rooted in some non-System Folder:

  1. In the IDE, set the StartupDirectory to the Desktop.
  2. In the Form's Load event, set the RootItem to the desired Folder, as in:
  3. ExpTree1.RootItem = CShItem.GetCShItem("C:\MyAppData")

ExpTree Methods

Method Type Remarks
RefreshTree N/A Rebuild tree through SelectedNode
ExpandANode Boolean Expands tree through input Path or CShItem

The methods RefreshTree and ExpandANode are not needed for basic usage and are discussed later.

ExpTree Events

StartUpDirectoryChanged Used for design-time interaction
ExpTreeNodeSelected Raised when TreeNode is selected

The EventArgs for ExpTreeNodeSelected are a string containing the full path of the underlying folder and the CShItem representing the SelectedNode.

Assume you have a form with an ExpTree named ExpTree1, a ListView named lv1, and a StatusBar named sbr1. To use the control, you must Import a few items:

Imports ExpTreeLib
Imports ExpTreeLib.CShItem
Imports ExpTreeLib.SystemImageListManager

Public Class frmExplorerLike
   Inherits System.Windows.Forms.Form

In the Form New routine, set up to use the image lists:

'Add any initialization after the InitializeComponent() call
SystemImageListManager.SetListViewImageList(lv1, True, False)
SystemImageListManager.SetListViewImageList(lv1, False, False)

The SetListViewImageList statements set the ListView's LargeImageList and SmallImageList to be the corresponding System image lists.

Add the following event handler:

Private Sub lv1_VisibleChanged(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles lv1.VisibleChanged
    If lv1.Visible Then
        SystemImageListManager.SetListViewImageList(lv1, True, False)
        SystemImageListManager.SetListViewImageList(lv1, False, False)
    End If
End Sub

To change the contents of the ListView when a node is selected in ExpTree1, declare an event handler as follows:

Private Sub AfterNodeSelect(ByVal pathName As String,
        _ ByVal CSI As CShItem) Handles
    ExpTree1.ExpTreeNodeSelected Dim dirList As New
    ArrayList() Dim fileList As New
    ArrayList() Dim TotalItems As
    Integer If CSI.DisplayName.Equals(CShItem.strMyComputer) Then
        'avoid re-query since only has dirs
        dirList = CSI.GetDirectories 
    Else
        dirList = CSI.GetDirectories
        fileList = CSI.GetFiles
    End If
    TotalItems = dirList.Count + fileList.Count
    If TotalItems > 0 Then
        Dim item As CShItem
        dirList.Sort()
        fileList.Sort()
        Me.Text = pathName
        sbr1.Text = pathName & "                 " & _
                    dirList.Count & " Directories " & _
                    fileList.Count & " Files"
        Dim combList As New ArrayList(TotalItems)
        combList.AddRange(dirList)
        combList.AddRange(fileList)

        'Build the ListViewItems & add to lv1
        lv1.BeginUpdate()
        lv1.Items.Clear()
        For Each item In combList
            Dim lvi As New ListViewItem(item.DisplayName)
            With lvi
              '
              ' SubItem formatting and adding to lvi omitted from 
              '    article text
              '
              'Set ListViewItem's IconIndex 
              '(and add Icon to lists if necessary)
                .ImageIndex = _
                 SystemImageListManager.GetIconIndex(item, False)
                .Tag = item
            End With
            lv1.Items.Add(lvi)
        Next
        lv1.EndUpdate()
    Else
        sbr1.Text = pathName & "Has No Items"
    End If
End Sub

The test for "strMyComputer" at the beginning uses the local system string for "My Computer" to avoid the annoying re-query of the A: drive each time "My Computer" is selected. Some special handling is required in order to get the folders and files sorted as Windows Explorer does. That special handling is done in CShItem's IComparable.CompareTo routine which is called when we sort the Directory and File ArrayLists.

Icon fetching and adding to both image lists is handled by the call to GetIconIndex. GetIconIndex's second parameter is set to False to indicate that the "Open" IconIndex should not be fetched.

Note: the download demo obtains and sets the icon indices in a separate thread. The code shown above is a simplified, single thread approach.

SystemImageListManager Class

This class is based on System image lists. It accesses two System image lists, one containing small icons, and one with large icons. The lists are synchronized such that the same IconIndex refers to the same icon in each list. When queried for an IconIndex of a CShItem, it determines if the icon should have an overlay and, if so, adds the icon with overlay as an additional icon in the System image lists. Since the IconIndex reported by SHGetFileInfo is not necessarily the actual IconIndex for the folder or file (which may need to use the icon plus overlay version), I use a HashTable to store the actual IconIndex. The HashTable key is based on the reported IconIndex, modified to reflect any additional overlays. In actual practice, the number of icons stored in the System image lists and the size of the HashTable is fairly small.

The SystemImageListManager class contains only Shared properties and methods. Since it is managing an external resource (two System image lists), only Shared properties and methods are needed or appropriate. Do not modify these two System image lists in any fashion outside of SystemImageListManager.

Any of SystemImageListManager properties or methods will call the class' Initializer routine.

SystemImageListManager Initializer

Private Shared Sub Initializer()
    If m_Initialized Then
        Exit Sub
    End If
    Dim dwFlag As Integer = SHGFI.USEFILEATTRIBUTES Or _
                    SHGFI.SYSICONINDEX Or _
                    SHGFI.SMALLICON
    Dim shfi As New SHFILEINFO()
    m_smImgList = SHGetFileInfo(".txt", _
                        FILE_ATTRIBUTE_NORMAL, _
                        shfi, _
                        cbFileInfo, _
                        dwFlag)
    If m_smImgList.Equals(IntPtr.Zero) Then
        Throw New Exception("Failed to create Small ImageList")
    End If
    '
    ' Identical code as above using SHGFI.LARGEICON 
    '   Omitted from Article text ... see source code
    '
    m_Initialized = True
End Sub

Initializer checks that this is the first call. If not, it assumes all is set up. If the first call, it obtains the Handle of a small and a large System image list, checking for success each time.

SystemImageListManager Properties

SmallList The Handle of the small ImageList
LargeList The Handle of the large ImageList

These ReadOnly properties may be of use to the application. The demo makes no use of them except internal to the class.

SystemImageListManager Methods

GetIconIndex

Public Shared Function GetIconIndex(ByRef item As CShItem, _
                       Optional ByVal GetOpenIcon As Boolean = False, _
                       Optional ByVal GetSelectedIcon As Boolean = False _
                       ) As Integer

GetIconIndex returns the IconIndex in both System image lists of the icon needed for the CShItem item. The Optional parameter GetOpenIcon instructs GetIconIndex to return the "Open" icon for the CShItem rather than the "Normal" icon. The Optional parameter GetSelectedIcon requests the "Selected" icon.

Internally, SystemImageListManager maintains a HashTable whose Key is based on the System imagelist IconIndex, the Link and Shared states of the referenced CShItem, and the "Open" or "Selected" state of the icon. The Value stored in the HashTable is the IconIndex of the icon in the System image lists.

If the IconIndex is already known (in the HashTable), the function simply returns the HashTable Value as the function value.

If the desired icon is not known (in the HashTable) and if the icon will contain overlays, the function obtains the icon using SHGetFileInfo, stores it in the System image lists, enters the IconIndex into the HashTable, and returns the IconIndex and returns it as the function value.

If the desired icon does not have overlays, then the IconIndex already stored in the CShItem is the correct IconIndex. In this case, the function stores the IconIndex into the HashTable, and returns it as the function value.

GetIcon

Public Shared Function GetIcon(ByVal Index As Integer, _
       Optional ByVal smallIcon As Boolean = False) _
       As Icon

Returns a GDI+ copy of the Large (default) or Small icon from the imagelist at the specified Index.

SetListViewImageList

Public Shared Sub SetListViewImageList( _
      ByVal listView As ListView, _
      ByVal forLargeIcons As Boolean, _
      ByVal forStateImages As Boolean)

This method attaches the appropriate System image list to the ImageList of a ListView. The forLargeIcons parameter selects which list to attach to which (True for large, False for small). I have not done anything with the forStateImages parameter except to pass it as False, always.

SetListTreeViewImageList

Public Shared Sub SetTreeViewImageList( _
    ByVal treeView As TreeView, _
    ByVal forStateImages As Boolean)

This method attaches the appropriate System image list to the ImageList of a TreeView. I have never tested or used the forStateImages parameter, except to set it to False.

Both of these methods use the SendMessage API to send a message to the control, attaching the System image list as the control's ImageList. See the source code for details. Note that .NET is not aware of this attachment. If the control is hidden and then shown, the attachment must be reestablished in a VisibleChanged event handler.

CShItem Class

Version 2 Changes

The CShItem class is the main class of the ExpTree library. Additions to any part of the library generally require changes to it. The major change between version 2 and previous versions actually occur in CShItem. Each CShItem that represents a folder maintains an ArrayList of CShItems representing the sub-folders that it contains. In versions prior to version 2, that ArrayList was never updated. If the method GetDirectories was called with the parameter Optional Refresh As Boolean = False set to True, the entire ArrayList was discarded and recreated. In version 2, the parameter Optional doRefresh As Boolean = True instructs GetDirectories to call a new function, RefreshDirectories. RefreshDirectories checks for changes and creates new CShItems for added directories, and deletes the CShItem representing directories that no longer exist. This process, implemented by other new code, is done in a low-cost fashion, so that this directory refresh can be done frequently. In ExpTree, it is done at every TreeNode expand, every select, and in several other cases relating to Drag and Drop.

Note that CShItems representing Files are not kept, but regenerated at each GetFiles or GetItems request. A potential line of study is to look at the memory versus processor time tradeoffs involved in treating file CShItems similar to directory items.

Version 3.00 of this class is completely different in its approach.

Constructors

Version 2 depreciates the use of New as a method of obtaining CShItems.

In version 2, the Sub New(ID As CSIDL) and Sub New(path As String) routines are still supported. However, Version 3.00 does not support any Public Sub New. For Version 2, the preferred replacement functionality is provided by the GetCShItem routines described here:

  • GetCShItem(ByVal ID As CSIDL) As CShItem

    CSIDL is an Enum representing System Special Folders, declared in the ShellDll class. ExpTree defines a subset of those as valid for its purposes. Unlike the equivalent Sub New, there is no restriction on which CSIDL may be used, beyond that of simple availability on a particular OS. Usage is illustrated by this code fragment which obtains the CShItem for My Computer.

    Dim special As CShItem
       special = GetCShItem(CSIDL.DRIVES)
  • GetCShItem(ByVal path As String) As CShItem

    path is a valid directory path (for example, "C:\ "). Path can be any CShItem.Path property, including GUIDs. A simple use is illustrated by this code fragment which obtains the CShItem for a specific Directory:

    Dim special As CShItem
       special = GetCShItem("C:\Temp\Test")

Properties

Property Type Remarks
DisplayName String Display name
Path String Full path (see note 1)
TypeName String Type of item (see note 2)
FullName String Full name of items (see note 3)
IconIndexNormal Integer Index into SystemImageList
IconIndexOpen Integer Index into SystemImageList
HasSubFolders Boolean May have sub-folders
IsBrowsable Boolean Can be browsed in place
IsDropTarget Boolean Items can be dropped here
IsFileSystem Boolean Is part of file system
IsFolder Boolean Is a folder
IsDisk Boolean Is a disk
IsLink Boolean Is a shortcut
IsRemovable Boolean Is a removable device
IsReadOnly Boolean Is ReadOnly
IsShared Boolean Is Shared
IsSystem Boolean Is a System file
LastWriteTime DateTime See FileInfo documentation
LastAccessTime DateTime See FileInfo documentation
CreationTime DateTime See FileInfo documentation
Length Long Size in bytes of a file
CanCopy Boolean Item can be copied
CanDelete Boolean Item can be deleted
CanLink Boolean Item can have a link created for it
CanMove Boolean Item can be moved
PIDL IntPtr Usable in SHGetFileInfo
clsPidl cPidl A class for manipulating PIDLs as Byte()
strMyComputer String "My Computer" on this computer
strSystemFolder String "System Folder" on this computer
DesktopDirectoryPath String The path of user's Desktop directory

All properties are ReadOnly.

Note 1: For File System objects, the FullPath property is just that, the full path. For non-File System objects, the full path may be a GUID.

Note 2: TypeName is the type name reported by SHGetFileInfo.

Note 3: FullName is usually the same as DisplayName. However, in the case of .lnk files, DisplayName does not include the .lnk extension. Fullname does. Given a link file whose Path is "C:\Temp\ABC.txt.lnk", Displayname will return "ABC.txt", FullName will return "ABC.txt.lnk".

The IconIndex... properties report the base IconIndex into the System image list. This is not directly useful to applications unless only non-overlay icons are desired.

In almost all cases, PIDL should not be used. It is visible only because SystemImageListManager needs to refer to it. PIDL may be useful if the application needs to call certain Shell .dlls. The clsPidl property is an instance of the cPidl class. It exposes methods of examining the PIDL as a Byte(). See the source code for further information.

The ...Time properties and the Length property are exactly the same as returned by the FileInfo class. I cheat and create a FileInfo instance to retrieve these values when any one of them is requested.

The str... properties provide the strings that represent "My Computer" and "System Folder" on the computer running the application. This provides a Locale neutral method of testing for these special names. The special name "Desktop" is provided by the DisplayName of the CShItem returned by GetDeskTop and is also Locale neutral.

Methods

Method Return Type Return Value
Shared Method    
GetCShItem CShItem See above for description
GetDeskTop CShItem The Desktop
Instance Methods    
GetDirectories ArrayList of CShItems All folders in a CShItem
GetFiles ArrayList of CShItems All files in a CShItem
GetItems ArrayList of CShItems All files and folders in a CShItem
RefreshDirectories Boolean True if any changes were made.
ToString String DisplayName
DebugDump None Writes info to the Debug console

GetDeskTop returns the one and only CShItem of the Desktop. The class maintains this CShItem internally, building it when the class is first accessed in any way. GetDeskTop returns the actual CShItem, not a clone.

GetDirectories, GetFiles, and GetItems return an ArrayList of CShItems as requested. If there are none of the requested types in a folder, they return an empty ArrayList. If the CShItem represents a file, an empty ArrayList is returned. An empty ArrayList is also returned on common error conditions, for example, a Not Ready disk (an empty CD drive, for example). Unlike Windows Explorer, the class does not post an Abort-Retry message box in those cases. Previous versions, before version 2, would throw an exception for unexpected errors occurring in the internal routine called by these methods, only when compiled in Debug mode. Version 2 and above will no longer throw an exception, returning an empty ArrayList on any error condition.

RefreshDirectories ensures that the ArrayList returned by GetDirectories reflects the current state of the file system. It returns True if there were any changes. RefreshDirectories is called by GetDirectories (unless specifically instructed not to by an Optional parameter), so there is seldom a need to call it directly.

ExpTree Control

Finally, we get to the control itself. Given the CShItem and SystemImageListManager classes, the control is fairly simple.

ExpTree Properties and Events

Property  
AllowDrop Allows (True) or Prevents (False) Drops onto the Tree.
StartUpDirectory Must be a CSIDL for a special folder.
RootItem Sets the root of the Tree to a CShItem.
SelectedItem Returns CShItem of current SelectedNode.
ShowHidden Allows/Disallows the showing of hidden directories in the TreeView.
ShowRootLines Allows/Disallows a line and expansion/compression box to be shown in the TreeView.
Events  
ExpTreeNodeSelected Raised when a TreeNode is selected.
StartUpDirectoryChanged Raised when the StartUpDirectory property is set.
Methods
ExpandANode Expands tree through the node representing the input CShItem. Returns False on failure to expand.
RefreshTree Reinitializes tree and expands it through the previously selected node.

StartUpDirectory is a CSIDL representing a System Special Folder. The list of folders that the control can deal with is available to the IDE via ExpTree's Property Sheet.

RootItem is a Run-Time only property. Setting this Item via a run-time call results in re-setting the entire tree to be rooted in the input CShItem. The CShItem must be a valid CShItem of some kind of folder (File Folder or System Folder). Attempts to set it using a non-folder CShItem are ignored. Usage of this property is illustrated in the demo in the ListView's MouseUp event and in the code behind the "C:\ Test" button.

ExpTreeNodeSelected is the event fired when the TreeView's AfterSelect event occurs. This notifies the containing Form of the event. The Event signature is:

Public Event ExpTreeNodeSelected(ByVal SelPath As String, _
             ByVal Item As CShItem)

where Item is the CShItem representing the selected node, and SelPath is the path of that CShItem. In the case of Virtual Folders, where the path is a GUID, SelPath contains the DisplayName of the CShItem.

StartUpDirectoryChanged is the event fired when the initial directory is set. It is Public in case the containing Form needs notification of this event. Normally, this is not needed since a change to the root of the Tree always selects the new root and thus fires the ExpTreeNodeSelected event.

ExpandANode is a revision of the ExpandANode originally presented in the forum for this article. Internally, this method is very different from the original and is not limited to File System directories as was the original. Any CShItem may be used as the input path. Unlike the original, this version will not force the Tree to be rooted on either the Desktop or My Computer. This version leaves the original Tree root in place. The method expands the tree from the tree root, expanding nodes as necessary through the input CShItem. Its signature is:

Public Function ExpandANode(ByVal newItem As CShItem) As Boolean

The method returns True if the expansion was successful, False otherwise. The class provides an alternate ExpandANode which takes a Path as its argument. The alternate signature method calls GetCShItem, checks the return, and calls the other ExpandANode with the returned CShItem.

RefreshTree is a method which causes the entire tree to be recreated and then expanded down to the original (prior to RefreshTree call) selected node. This allows the Tree to reflect changes made to the directory structure external to the control. If the originally selected node is no longer valid, for example, it and/or some earlier part of its path were deleted or renamed, the tree is expanded through the lowest valid point in its original path. This method's code is almost identical to the code presented by Calum McLellan in the forum. One difference is that it now defaults to rooting the Tree in the original Tree root rather than defaulting to the Desktop. Another difference is that it suppresses the raising of ExpTreeNodeSelected events until the refresh has completed. This method benefits from the new version of ExpandANode in that it is no longer limited to dealing only with File System directories.

The signature of this method is:

Public Sub RefreshTree(Optional ByVal root As CShItem = Nothing)

The optional parameter root allows for dynamic resetting of the Tree root as part of the refresh operation.

ExpTree Code

In the initialization of ExpTree, we set the TreeView's ImageList and add the control's handler for changes to StartUpDirectory.

'Add any initialization after the InitializeComponent() call
  SystemImageListManager.SetTreeViewImageList(tv1, False)
  AddHandler StartUpDirectoryChanged, AddressOf OnStartUpDirectoryChanged
  OnStartUpDirectoryChanged(m_StartUpDirectory)

The Public Property StartUpDirectory starts the work when the StartUpDirectory is set or changed:

Private m_StartUpDirectory As StartDir = StartDir.Desktop

<Category("Options"), _
 Description("Sets the Initial Directory of the Tree"), _
 DefaultValue(StartDir.Desktop), Bindable(True)> _
   Public Property StartUpDirectory() As StartDir
        Get
           Return m_StartUpDirectory
        End Get
        Set(ByVal Value As StartDir)
        If Array.IndexOf(Value.GetValues(Value.GetType), _
         Value) >= 0 Then
            m_StartUpDirectory = Value
            RaiseEvent StartUpDirectoryChanged(Value)
        Else
            Throw New ApplicationException( _
            "Invalid Initial StartUpDirectory")
        End If
    End Set
End Property

The property attributes give the designer information. The code at If Array.IndexOf... compares the input Value with the Enum's allowable values and Throws an exception if not valid. If valid, the private version of the property is set and a StartUpDirectoryChanged Event is raised.

The real work is done in the OnStartUpDirectoryChanged event handler:

 Private Sub OnStartUpDirectoryChanged(ByVal newVal As StartDir)
   If Not IsNothing(Root) Then
       ClearTree()
   End If
   Dim L1 As ArrayList
   Dim special As CShItem
   special = GetCShItem(CType(Val(m_StartUpDirectory), ShellDll.CSIDL))
   Root = New TreeNode(special.DisplayName)
   BuildTree(special.GetDirectories)
   Root.ImageIndex = SystemImageListManager.GetIconIndex(special, _
    False)
   Root.SelectedImageIndex = Root.ImageIndex
   Root.Tag = special
   tv1.Nodes.Add(Root)
   Root.Expand()
End Sub

Private Function BuildTree(ByVal L1 As ArrayList)
  L1.Sort()
  Dim CSI As CShItem
  For Each CSI In L1
      If Not (CSI.IsHidden And Not m_showHiddenFolders) Then
          Root.Nodes.Add(MakeNode(CSI))
      End If
  Next
End Function

Private Function MakeNode(ByVal fi As CShItem) As TreeNode
  Dim newNode As New TreeNode(item.DisplayName)
  newNode.Tag = item
  newNode.ImageIndex = SystemImageListManager.GetIconIndex(item, False)
  newNode.SelectedImageIndex = SystemImageListManager.GetIconIndex(item, True)
  If item.IsRemovable Then             
      newNode.Nodes.Add(New TreeNode(" : "))
  ElseIf item.HasSubFolders Then
      newNode.Nodes.Add(New TreeNode(" : "))
  ElseIf item.GetDirectories.Count > 0 Then   
      newNode.Nodes.Add(New TreeNode(" : "))  
  End If
  Return newNode
End Function

Private Sub ClearTree()
  tv1.Nodes.Clear()
  Root = Nothing
End Sub

First, the folders of the base are fetched and sorted. For each folder in the base, we create a new TreeNode with the correct icons, and add it to the Root node. Note that each TreeNode's Tag is set to the CShItem that it belongs to. If the sub-folder may have sub-folders of its own, we create a dummy node and add it to the sub-node, so the Treeview will show a "+" and allow expansion.

The code in BuildTree that checks .IsHidden prevents Hidden directories from being shown in the TreeView if the ShowHiddenFolders property is False. The If ... ElseIf sequence in MakeNode avoids checking floppy drives so as to prevent the annoying floppy access. It also works around the fact that a directory with all hidden members will be reported by .HasSubFolders as False. Finally, we attach the Root node to the TreeView and Expand the root to get the final display.

The BeforeExpand Event of the Treeview is very similar to the code described above. The interesting part is:

Private Sub tv1_BeforeExpand(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) _
    Handles tv1.BeforeExpand
    Dim oldCursor As Cursor = Cursor
    Cursor = Cursors.WaitCursor
    If e.Node.Nodes.Count = 1 AndAlso _
     e.Node.Nodes(0).Text.Equals(" : ") Then
        e.Node.Nodes.Clear()
        Dim CSI As CShItem = e.Node.Tag
        Dim D As ArrayList = CSI.GetDirectories(m_refresh)
        If D.Count > 0 Then
            '.... processing steps omitted
        End If
    Else
        RefreshNode(e.Node)
    End If
    Cursor = oldCursor
End Sub

If the node is a dummy node, then clear it and process it similar to the code described above.

Otherwise, assume that the sub-nodes have already been set up and call RefreshNode, ensuring that the content matches reality. Note that if there are no sub-folders for this node, then the TreeNode will be cleared, removing the "+" and preventing future expansion.

Lastly, we have the AfterSelect Event which passes the CShItem from the SelectedNode to the containing Form.

Private Sub tv1_AfterSelect(ByVal sender As System.Object, _
        ByVal e As System.Windows.Forms.TreeViewEventArgs) _
        Handles tv1.AfterSelect
  Dim node As TreeNode = e.Node
  Dim CSI As CShItem = e.Node.Tag
  If CSI Is Root.Tag AndAlso Not tv1.ShowRootLines Then
      With tv1
          .BeginUpdate()
          .ShowRootLines = True
          RefreshNode(node)
          .ShowRootLines = False
          .EndUpdate()
      End With
  Else
      RefreshNode(node)
  End If
  If EnableEventPost Then 'turned off during RefreshTree
      If CSI.Path.StartsWith(":") Then
          RaiseEvent ExpTreeNodeSelected(CSI.DisplayName, CSI)
      Else
          RaiseEvent ExpTreeNodeSelected(CSI.Path, CSI)
      End If
  End If
End Sub

The SelectedNode is updated by RefreshNode. The test for ShowRootLines works around a display problem that arises when the tested condition is True. If event posting has not been suppressed for RefreshTree, then raise the ExpTreeNodeSelected event, passing the CShItem and the path of the node. Note that some System Folders' path is a GUID. In that case, we return the DisplayName of the SelectedNode rather than the path. The true path is still available in the CShItem.

The Demo Program and other thoughts

The demo Forms do no useful work except illustrating the usage of the control and classes presented here. I really wasn't trying to duplicate Windows Explorer. There are two Forms in the Demo package. frmExplorerLike (shown above and described here) exists only to illustrate how to use some of the methods available in ExpTreeLib. frmDragDrop is a bit more realistic and and illustrates Drag From and Drop To ExpTree as well as Drag From the ListView.

Left-click a folder in the ListView to cause the corresponding folder in the Tree to be expanded and that folder's contents to be displayed in the ListView.

frmExplorerLike shows three run-time methods of changing the root of the Tree. Right-clicking a folder in the ListView will cause that folder to become the new Tree root. It also will fill the ComboBox with the names of the parents of that folder. Selecting one of the entries in the combobox will set the Tree root to that folder. In other words, it provides a way to get back to the original Tree root.

Clicking the "C:\ Test" button will cause the Tree root to become C:\. I provide no way to navigate back to the original Tree root in this case. Given the RootItem property of ExpTree and the GetCShItem(Path as String) method of CShItem, the code to accomplish this change in the demo program is trivial.

Private Sub cmdCTest_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdCTest.Click
    Dim cDir As New CShItem = GetCShItem("C:\")
    If cDir.IsFolder Then
        ExpTree1.RootItem = cDir
    End If
End Sub

Click the Refresh button on frmExplorerLike and RefreshTree will be invoked. You may test this feature by creating a test directory tree, running the demo, navigating to the bottom of the test tree, deleting some or all of the test directory tree via Windows Explorer, and then clicking the Refresh button.

Both Demo Forms now gather icon indices, for display in its ListView, in a separate thread which improves initial startup and general responsiveness. This is not shown is the code presented in this article. See the demo for the actual code.

Feedback from readers of this article has been very helpful in making this control better. A look at the History section and the forum will show that multiple feature additions and bug fixes came as a direct result of that feedback. Thanks everyone.

Credits

My original article contained a class for accessing System image lists. Some important fragments of that class survive in SystemImageListManager. The original was simply a translation from C# to VB.NET of some of Steve McMahon's System image list class which may be found here. Steve's class has substantial additional capabilities for drawing icons and attaching them to other types of controls.

Calum McLellan has made significant contributions that improved this control. Calum's article Explorer ComboBox and ListView in VB.NET extends this library with both ComboBox and ListView classes.

History

  • 04/20/2012 -- Version 2.12.1 Updated Downloads to include Windows Explorer style sorting to the displayed File/Folder names. This change added a new class (StringLogicalComparer) and a one line change to CShItem to use that Class. Result is: xx11xx sorts before xx101xx.
  • 04/20/2012 -- Version 2.12. Update of Downloads and article to include:
    • ASUS fix which also probably applies to Carbonite and several other Cloud backups that are implemented as Shell Extensions.
    • Proper handling of Zip and other Compressed files on Vista/Win7 (ensure they are treated a Files, not Folders)
    • Proper handling of AllowDrop property in ExpTree. Can now be usefully set in IDE.
    • Changed the definition and source of CShItem.Attributes such that this property now is set from a FileInfo or DirectoryInfo. The definition is now a System.IO.FileAttributes (as it always should have been).
    • Made CShItem.HasSubfolders a fill on demand property, avoiding the cost of retrieval when not needed. A big win in some cases.
    • Modified the handling of the HasSubFolders attribute to compensate for difference between XP and Vista/Win7. On Vista/Win7 client systems this is a dramatic improvement in responsiveness.
    • Minor changes to eliminate some harmless compiler Warnings.
    • Removal of dead and debug code and correction of some comments.
  • 03/12/2006 -- Version 2.11. Updated to support VS2005. Deleted the Application.DoEvents call in CShItem.GetContents as discussed in the forum.
  • 09/16/2005 -- Version 2.1. Update to source and demo to make equal to same files in Part 2 of this article. Minor fix to this article's code, larger fix to code covered in Part 2.
  • 08/23/2005 -- Version 2 release - Update to article, source, and demo.
    • Changed directory refresh strategy to update cached directories in GetDirectories unless specifically prevented via an Optional parameter.
    • Added CShItem.GetCShItem to replace functionality of Sub New(ID as CSLID) and Sub New(Path As String).
    • Added refresh of node content in BeforeExpand and AfterSelect events.
    • Added Drag and Drop -- not discussed in this article.
    • Added the properties ShowHiddenFolders and ShowRootLines to ExpTree.
    • In ShellDll, changed declaration of POINT from Private to Public which may break existing code. Fully specify System.Drawing.Point or ShellDll.Point as appropriate.
    • Added ability to get the selected IconIndex as well as the normal and open ImageIndices to SystemImageListManager.
    • Added ability to get a true Small Icon from GetIcon -- From Calum McLellan.
    • Added many additional properties and methods to CShItem.
    • By popular demand, removed the Throw of an error for certain conditions from CShItem when compiled under DEBUG.
    • Multiple small improvements, some bug fixes, along with some code reorganization.
  • 04/02/2005 -- Update to source and demo.
    • Modified ExpTree control to paint the tree when initially dropped on a form in the IDE, and to hide from the IDE those properties that can not be changed there.
    • Changed the sort order of CShItems such that "My Documents" appears before "My Computer" in the tree.
    • Added the Public Shared field strMyDocuments to CShItem which contains the Locale representation of the string "My Documents".
    • Added the Public ReadOnly Property IsHidden to CShItem. (Thanks Calum.)
    • Modified SystemImageListManager to get and use the actual Small Icon for Small Image Lists, rather than re-using the Large Icon (Thanks Calum).
    • Fixed the demo to use SystemImageListManager to set icon indices for the ListView. This was broken when threading was added to the demo.
  • 03/02/2005 -- Update to source, demo, and article.
    • Rewrote ExpandANode to remove limitations of previous version.
    • Added RefreshTree method to allow application to force a rebuilding of the Tree to display changes to the directory structure.
    • Added SelectedItem property which returns the CShItem of the current SelectedNode.
    • Initialized the HideSelection property of the TreeView to False.
    • Modified Sub New(path as String) to accept any CShItem.Path, including GUIDs.
    • Fixed XP related problem which suppressed display of ZIP files.
    • Removed (in Release compilations only) the throwing of an exception for unexpected errors.
    • Added threading to the demo program to improve responsiveness.
  • 01/11/2005 -- Update to correct bug that prevented the creation of CShItems in a worker thread.
  • 01/09/2005 -- Update to correct a bug and to incorporate some additional features.
    • Fixed the routines ItemIDListSize and PidlCount in CShItem which would fail in some very rare instances.
    • Modified Sub New(path as String) in CShItem to accept a file path as well as a directory path.
    • Added the required special handling so that My Documents can be used as a base directory. Code changes in Sub New(StartDir as CSIDL) to accomplish this. Also uncommented StartDir entry for My Documents so it can be used in the designer.
    • Improved GetItems() property to avoid an extra pass over the contents of a directory.
    • Added ExpandANode method to ExpTree. This is the limited version of this method discussed in the forum for this article.
    • Modified demo to clear the ListView when an empty directory is selected in the TreeView. Demo also contains some commented out code that may be used to exercise some of the added functionality. See demo source for directions on how to do this.
  • 11/29/2004 -- Added features to CShItem, ExpTree, and the demo program. Small addition to ShellDll. Made CShItem and the demo more Culture neutral. Modified article to reflect changes.
    • Added a variant constructor to CShitem to allow the creation of a CShItem based on a valid directory path (e.g. -- "C:\").
    • Added a Run-Time only property to ExpTree to allow the changing of ExpTree's root directory dynamically.
    • Modified demo to illustrate the new properties.
    • Removed or modified tests based on CShItem's TypeName and DisplayName strings that would fail in a non-English Culture setting. Also modified a test in the demo which would fail under the same circumstance. Modified the creation code for the Desktop CShItem to set its path to its GUID and to obtain its DisplayName from SHGetFileInfo rather than arbitrarily setting it to "Desktop".
    • Modified CShItem such that SHGetFileInfo is not called until the property values that it provides are actually requested. This was done similarly to changes in a previous update that deferred the fetching of IconIndexes until actually requested.
  • 11/05/2004 -- Update to the CShItem source. This fixes the following problems:
    • A memory leak in the GetContents method.
    • Changed the fetching of IconIndexes so that they are not obtained until called for. This should make little difference to applications that need icons for all files, but significantly speeds up applications that do not use or need icons for most files.
    • Fetch the correct icon for Open folders. It was finding and using the icon for MyDocuments for this purpose rather than the correct one.
    • Source download contains the correct code to match the article. This was not posted correctly for the last update prior to this one.
  • 10/22/2004 -- Noticed that SystemImageListManager's inappropriate design as a class with potential multiple instances caused problems, especially within the IDE, under some circumstances. Recast it as a class with only Shared properties and methods, as it should have been designed in the first place. With this change, and with the addition of a Mutex around the code that actually writes to the System image list, the class should be ThreadSafe, though that has not been tested exhaustively.
  • 10/20/2004 -- Second version. Correct display of alpha channel icons on XP systems. One, simplified and corrected, class for managing icons. Revision of article to reflect code changes.
  • 10/11/2004 -- Initial version of article and ExpTreeLib (Ver. 1.0.1743.41270).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jim Parsells
United States United States
Member
After 30+ years working in the IT field, mostly managing SysAdmins, I have retired. One of my hobbies returns me to programming, basically just to keep my hand in.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberfredatcodeproject28 Oct '12 - 6:27 
it worth a 5
GeneralMy vote of 4memberfredatcodeproject27 Oct '12 - 7:13 
pretty good
GeneralRe: My vote of 4memberJim Parsells27 Oct '12 - 10:39 
You might like Version 3 a bit more. ExpTreeLib Version 3 - Explorer-like Navigation and Operation for your Forms[^]
GeneralRe: My vote of 4memberfredatcodeproject28 Oct '12 - 6:26 
Thanks Jim
QuestionLitte bug in demo applicationmembercadprof25 Aug '12 - 7:07 
Hi Jim,
 
I found a little bug in your demo application (version 3). If you select (not expand) a folder in tree view having folders in it. From the context menu of listview, add a new folder. The new folder appears correctly in the list view. Now expand the node in the tree view, only the new folder is listed. The problem doesn't occur if you expand the node before adding the new folder.
 
If you find a solution about that, let me know,
 
Thanks a lot for your good job,
 
André
AnswerRe: Litte bug in demo applicationmemberJim Parsells26 Aug '12 - 7:08 
You are correct. I found the problem and have a fix for it.
Via email, send me your address so that I can send you the revised ExpTree.vb. It is a bit too messy to just post here - I did a little refactoring of the code to make this fix without repeating blocks of code.
 
It would be much better if you posted future messages under the Version 3.0 article rather than here - just to keep the V3 related stuff where it is relevant.
QuestionUpdate to version 3membercadprof22 Aug '12 - 9:56 
HI,
 
Thank you for your code, it is very valuable. I use that in one of my project, but now I would like to update to version 3. It is very different. In version 2, it was possible to filter the files selection using a file pattern (ex. CSI.GetFiles("*.DWG"). It doesn't seem to work now, either with GetItems. Is it possible to do the same thing with version 3?
 
Best regards,
 
André
AnswerRe: Update to version 3memberJim Parsells22 Aug '12 - 13:20 
The Filtered version of GetFiles in version 2.x was a rather silly, mostly undocumented, convenience routine in CShItem. I actually think it was a one off addition made to satisfy a request by one user that got into the published version by mistake.
In fact, the V2.x routine is not really OK for the general case! It uses the Directory.GetFiles method to get a list of files which match the search pattern. See the documentation of Directory.GetFiles to understand how this can go wrong in many circumstances, including the one you cite (it will return files whose extension starts with DWG - ie MyFile.dwgxxx).
 
A better approach would be to implement the filter in the application code after obtaining the list of Files.
To implement in CShItem use the following UNTESTED code:
    ''' <summary>
    ''' Returns the Files of this sub-folder, filtered by a filtering string, as an
    '''   ArrayList of CShitems
    ''' </summary>
    ''' <param name="Filter">A filter string (for example: *.Doc)</param>
    ''' <returns>An ArrayList of CShItems. May return an empty ArrayList if there are none.</returns>
    ''' 
    Public Function GetFiles(ByVal Filter As String) As ArrayList
        GetFiles = New ArrayList()
        If m_IsFolder Then
            Filter = Filter.ToLower
            For Each CSI As CShItem In Me.Files
                If CSI.DisplayName.ToLower Like Filter Then
                    GetFiles.Add(CSI)
                End If
            Next
        End If
    End Function
Again I say that I have not tested this code - try it and see. Let me know your results!
Note that I use the VB.Net "Like" operator which has its' own match critera - generally better for what it is being used for here, but --- Test!
Jim Parsells

GeneralRe: Update to version 3memberJim Parsells22 Aug '12 - 18:04 
I have now tested the function. It works as expected.
Once you know that the underlying Filter mechanism is the "Like" operator, you can make up some rather complex Filters. This may be useful in some cases.
Jim

GeneralRe: Update to version 3membercadprof24 Aug '12 - 11:20 
Thank you very much Jim,
 
I didn't create a function, for my needs I just modified the private sub AfterNodeSelect:
 
...
Dim combListTemp As ArrayList = CSI.GetItems
Dim combList As New ArrayList
Dim tempCSI As CShItem
 
For Each tempCSI In combListTemp
    If Not tempCSI.IsFolder Then
        If tempCSI.GetFileName.ToLower Like m_FilePattern.ToLower Then
            combList.Add(tempCSI)
        End If
    Else
        combList.Add(tempCSI)
    End If
Next
...
 
m_FilePattern is string private member variable expose as a property. ex "*.DWG".
 
It seems to work very well.
 
Regards,
 
André
GeneralRe: Update to version 3memberJim Parsells24 Aug '12 - 11:28 
That's probably how I would do it. I am glad it is working for you.
Jim

GeneralMy vote of 5memberBernieStuss29 Jul '12 - 0:47 
Very helpful for me to develop a larger project to edit video meta data and file data. Thanks for the comprehensive explanation. now I'll try to embed the code in my project.
Bernd
NewsNew Version available.memberJim Parsells16 Jul '12 - 11:15 
Version 3.00 of this code is now available at CodeProject.
Version 2.12 available on this page is quite adequate for many simpler purposes. However, Version 3.00 adds the following features:
  • Automatic update of the TreeView or ListView when any change is made by any process to the
    displayed folders or their content.
  • Dropping a file/folder onto ExpTree and/or the ListView. This feature supports normal Windows Control-Key modifiers
    and right button drag menu to instruct the receiving control to copy or
    move the file(s)/folders(s) to the target
    folder. The drag source may be itself or any application (including Windows Explorer)
    that provides at least FileDrop (CF_HDROP)
    DataFormatted information. One useful
    class of such applications is Windows email clients (Outlook, Outlook Express,
    Thunderbird, Windows Live Mail, etc.)
  • Dragging file(s)/folder(s) from the ExpTree or the ListView. Normal Windows
    Control-key and right button drag operations are supported. The drop
    target may
    be itself or any window (including Windows Explorer) that accepts at least
    FileDrop (CF_HDROP) DataFormatted information.
  • Right-click on a file/folder to display the same ContextMenu that Windows
    Explorer would display on right-click. File/folder rename
    support included.
  • Edit of the first column of the ListView to rename a file/folder -- if and only if the first
    column is
    the file/folder name.
  • Column-click sorting (with Sort Glyph) of the ListView columns when displayed in
    Details view.
  • Selected item(s) delete in response to the Delete key or Context Menu.
  • Double-click on a file to "Open" it.
  • Substantial optimization of both the library and demo forms relative to earlier
    versions.
 
If you have need of any of these features, you should look at the new version.
QuestionRight Click Handlingmemberitsme yudist27 May '12 - 22:53 
I try a lot of method on ExpTreeLib but I can't find the right code, is there any method to add right click handling on ExpTreeLib
GeneralRe: Right Click Handlingmemberjinzai2 Jul '12 - 0:14 
The order of events for mouse clicks is : MouseDown, Click, MouseClick, MouseUp. The Click event sends EventArgs and is raised for all mouse clicks, so...a Click event handler will consume all of them, but I do not know how you would distinguish left from middle from right buttons in a Click event handler. MouseClick receives MouseEventArgs, however. You could use this in ExpTreeLib...
 
    Private Sub OnMouseClick_tv1(ByVal sender As Object, _
                                 ByVal e As System.Windows.Forms.MouseEventArgs) _
                                 Handles tv1.MouseClick
        If e.Button = Windows.Forms.MouseButtons.Right Then
            MsgBox("Right click")
        End If
    End Sub

QuestionCan't get folder path of empty foldermemberjohnjsm23 May '12 - 5:12 
Hi,
First off I love your control. It has worked almost perfectly for me with one exception. If I select a folder tyhat is empty it won't return the path name. It only returns the path name if there is a file within that folder. I've looked through the code but can't figure out what to do. Its in the AfterNodeSelect where I get the pathname
 
Thanks
J
AnswerRe: Can't get folder path of empty foldermemberJim Parsells23 May '12 - 5:57 
More info needed!
Whether or not the Folder is empty will have no impact on getting the Path of a Folder. If you do not have the appropriate permissions to see a Folder, you won't be able to see it. Otherwise, there is no reason to have the problem that you report.
What steps are required to cause this apparent problem?
What version of ExpTreeLib are you using (should not actually make a differece, but...)?
What OS?
Is the Folder in question the one you are selecting in the Tree or is it a sub-Folder of the selected Node?
Does it show up for you in Windows Explorer?
Jim

GeneralRe: Can't get folder path of empty foldermemberjohnjsm23 May '12 - 21:02 
ok. This is the code from your example that I am using. I have replaced Me.Text with a text box called txtExpPath that displays the pathname. The file version is 2.12.4517.25556
Using Windows 7
It does not matter where the folder is created. So long as its empty it will not return the path name. All folders show up in Windows Explorer and I have full permissions to every folder.
 

Private Sub AfterNodeSelect(ByVal pathName As String, ByVal CSI As CShItem) Handles ExpTree1.ExpTreeNodeSelected
Dim dirList As New ArrayList()
Dim fileList As New ArrayList()
Dim TotalItems As Integer
LastSelectedCSI = CSI
If CSI.DisplayName.Equals(CShItem.strMyComputer) Then
dirList = CSI.GetDirectories 'avoid re-query since only has dirs
Else
dirList = CSI.GetDirectories
fileList = CSI.GetFiles
End If
SetUpComboBox(CSI)
TotalItems = dirList.Count + fileList.Count
Event1.WaitOne()
If TotalItems > 0 Then
Dim item As CShItem
dirList.Sort()
fileList.Sort()
'Me.Text = pathName & " " & _
' dirList.Count & " Directories " & fileList.Count & " Files"
txtExpPath.Text = pathName '

Private Sub AfterNodeSelect(ByVal pathName As String, ByVal CSI As CShItem) Handles ExpTree1.ExpTreeNodeSelected
Dim dirList As New ArrayList()
Dim fileList As New ArrayList()
Dim TotalItems As Integer
LastSelectedCSI = CSI
If CSI.DisplayName.Equals(CShItem.strMyComputer) Then
dirList = CSI.GetDirectories 'avoid re-query since only has dirs
Else
dirList = CSI.GetDirectories
fileList = CSI.GetFiles
End If
SetUpComboBox(CSI)
TotalItems = dirList.Count + fileList.Count
Event1.WaitOne()
If TotalItems > 0 Then
Dim item As CShItem
dirList.Sort()
fileList.Sort()
'Me.Text = pathName
txtExpPath.Text = pathName '& " " & _
' dirList.Count & " Directories " & fileList.Count & " Files"
 
Dim combList As New ArrayList(TotalItems)
combList.AddRange(dirList)
combList.AddRange(fileList)
 
'Build the ListViewItems & add to lv1
lv1.BeginUpdate()
lv1.Items.Clear()
For Each item In combList
Dim lvi As New ListViewItem(item.DisplayName)
With lvi
If Not item.IsDisk And item.IsFileSystem And Not item.IsFolder Then
If item.Length > 1024 Then
.SubItems.Add(Format(item.Length / 1024, "#,### KB"))
Else
.SubItems.Add(Format(item.Length, "##0 Bytes"))
End If
Else
.SubItems.Add("")
End If
.SubItems.Add(item.TypeName)
If item.IsDisk Then
.SubItems.Add("")
Else
If item.LastWriteTime = testTime Then '"#1/1/0001 12:00:00 AM#" is empty
.SubItems.Add("")
Else
.SubItems.Add(item.LastWriteTime)
End If
End If
'.ImageIndex = SystemImageListManager.GetIconIndex(item, False)
.Tag = item
End With
lv1.Items.Add(lvi)
Next
lv1.EndUpdate()
LoadLV1Images()
Else
lv1.Items.Clear()
'sbr1.Text = pathName & " Has No Items"
End If
End Sub
Dim combList As New ArrayList(TotalItems)
combList.AddRange(dirList)
combList.AddRange(fileList)
 
'Build the ListViewItems & add to lv1
lv1.BeginUpdate()
lv1.Items.Clear()
For Each item In combList
Dim lvi As New ListViewItem(item.DisplayName)
With lvi
If Not item.IsDisk And item.IsFileSystem And Not item.IsFolder Then
If item.Length > 1024 Then
.SubItems.Add(Format(item.Length / 1024, "#,### KB"))
Else
.SubItems.Add(Format(item.Length, "##0 Bytes"))
End If
Else
.SubItems.Add("")
End If
.SubItems.Add(item.TypeName)
If item.IsDisk Then
.SubItems.Add("")
Else
If item.LastWriteTime = testTime Then '"#1/1/0001 12:00:00 AM#" is empty
.SubItems.Add("")
Else
.SubItems.Add(item.LastWriteTime)
End If
End If
'.ImageIndex = SystemImageListManager.GetIconIndex(item, False)
.Tag = item
End With
lv1.Items.Add(lvi)
Next
lv1.EndUpdate()
LoadLV1Images()
Else
lv1.Items.Clear()
'sbr1.Text = pathName & " Has No Items"
End If
End Sub
GeneralRe: Can't get folder path of empty foldermemberJim Parsells24 May '12 - 5:52 
Ah, now I see what the problem is. This is a bug introduced between Version 2.11 and Version 2.12. Not a serious one, but a bug nonetheless.
In the original Form, make this change:
        Event1.WaitOne()
        Me.Text = pathName      'move line to here
        If TotalItems > 0 Then
            Dim item As CShItem
            dirList.Sort()
            fileList.Sort()
            'Me.Text = pathName      'move line from here
Make this change in both Demo Forms.
GeneralRe: Can't get folder path of empty foldermemberjohnjsm24 May '12 - 11:02 
Excellent. It's working perfectly now. Thanks a million for your help
GeneralMy vote of 5memberrctaubert19 Apr '12 - 18:30 
Fantastic work as usual.
QuestionDoesn't sort directory name like the real Explorermemberrctaubert19 Apr '12 - 18:29 
Great work as usual. You have been working with Treeviews for some time I think. I am sure I have some code from you from several years ago.
 
The first thing I noticed was that the folders were not sorted in the same manner as the real Explorer. (e.g. folder VS.net8 comes AFTER VS.net10)
 
Again, thanks for publishing this.
AnswerRe: Doesn't sort directory name like the real ExplorermemberJim Parsells20 Apr '12 - 5:44 
You are correct. My later versions of this code gained that facility early on, but somehow it never made its' way downlevel. I'll take a look at fitting it into this version.
You have one of my later versions (probably 2.14) with that facility, so I can understand why you are surprised at not seeing it here.
I am hoping to finish another article soon. That one will make my latest version (3.00) available. V3.00 is an improved version of the 2.14 "unpublished version" that you have.
Both 2.12 (this version) and 3.00 (the next to be published) have advantages, so V3.00 will get its' own article.
AnswerRe: Doesn't sort directory name like the real ExplorermemberJim Parsells20 Apr '12 - 17:04 
I added this to the downloads. It now sorts like Windows Explorer.
Thanks for pointing out the omission.
Jim

Questionapplication start up upon double clickmemberAmritanshu Kar20 Mar '12 - 1:47 
Respected Sir ,
 
thanks a lot for your previous help . Would like to bother you yet again .
I want exp tree to open up the application on a double click of an file icon . That is in the explorer view when i open up the folder . When I click on say a .doc or .pdf or .txt application , it should open up the file with the associated application like a windows explorer.
 
Sir Please do let me know upon how to go about it . Is there a code or a comment line there already ?
 
Thanks
 
regards
 
amritanshu
AnswerRe: application start up upon double clickmemberAmritanshu Kar20 Mar '12 - 2:18 
sir i got the answer to my problem from the forum bt still i would like to know upon the right click features , could u please mail me ur latest version , my id is amritanshu.kar@gmail.com
 
thanking you
QuestionUrgent help required : custom startup folder in tree viewmemberAmritanshu Kar14 Mar '12 - 22:53 
I am working on my major project of b.tech with this code. Is there a way in which i could add a custom directory as the startup directory in the tree view. I am creating an encryption program which requires a start up directory as say
 
c:/users/paul/My Documents/exptree crypt.
 
all my encrypted files will be stored here.
 
What I understand from the code is that the program only takes special folders as start up . also it only accepts integer values for a directory such as H1A etc , which are assigned to special folders.
 
Can there be a way in which this program accepts a path or a string as a start up directory .
 
thanks in advance for the help
AnswerRe: Urgent help required : custom startup folder in tree viewmemberAmritanshu Kar14 Mar '12 - 23:22 
BTW sir the code is amazing, solved half of my work , loving it , please so help me out . I know its a repeating question for you but i am novice and its one of my first projects .
AnswerRe: Urgent help required : custom startup folder in tree viewmemberJim Parsells15 Mar '12 - 5:42 
To set an ExpTree to appear to start Rooted in some non-System Folder:
1.In the IDE, set the StartupDirectory to the Desktop.
2.In the Form's Load Event, set the RootItem to the desired Folder, as in:
ExpTree1.RootItem = CShItem.GetCShItem("C:\MyAppData")

GeneralRe: Urgent help required : custom startup folder in tree viewmemberMember 874063219 Mar '12 - 20:38 
thanks a ton for your help .
GeneralMy vote of 5memberPaul van der Stel26 Feb '12 - 4:55 
Mon Dieu! J'adore!
But if i'm completely serious, I LOVE IT!
QuestionList View in Detail View is emptymemberPublic Property12 Feb '12 - 1:13 
Hello there,
 
thank you very much for this very cool control.
 
I have a question about the view properties of the listview control, that is connectet to the treeview. I have copied alle the code to my own project. i can switch between three of the four view properties. but if i switch to the "detail view" the listview still keeps empty.
 
I dont understand this, because i have copied the whole demo project and there the "detail view" is working.
 
best regards from germany!
AnswerRe: List View in Detail View is emptymemberJim Parsells12 Feb '12 - 16:13 
The most obvious thing I can think of is to ask if you have set up the Columns of the ListView in your project? For the Demo, I did that in the IDE. You can do that in the IDE or via code. Detail View will only show as many columns are are defined for the ListView, no matter how many ListViewSubItems you define in your code as you create the ListViewItems.
Jim

GeneralRe: List View in Detail View is emptymemberPublic Property12 Feb '12 - 22:12 
Hey Jim,
 
thank you very much. That was the problem. i guess that without columns the detail view cant display something.
QuestionAny idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberComputer_Guy_200731 Jan '12 - 12:53 
Hi Jim,
 
I downloaded and modified your code few years ago (around 2008) and used it in my VB.Net application as a means to allow my users to assign files to records. It works like this:
When the user opens a customer record, on the customer window I have an 'Attachments' button that calls ExpTreeLib when clicked. The call passes ‘CustomerAccountNumber” and ExpTreeLib opens the corresponding folder.
 
The code has been working great for many years (thank you very much Jim). Recently I installed it on a new Win7 32bit machine and suddenly it is painfully slow.
The files folder are on a common server (SBS Server 2003) with hundreds of subfolders used for this particular functionality..
On older slower machines (Win XP) it loads fairly fast, within 8 seconds max. But the new installs on Windows 7 Pro it is very slow, it takes 5+ minutes.
 
Just for testing I installed on a Win7 Home edition and it is very fast.
 
ExpTreeLib.dll is in the same folder as the .exe calling it. And I did register it using Regasm and I can see it in the register using Regedit.
 
Again thank you very much for posting the project.
AnswerRe: Any idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberJim Parsells31 Jan '12 - 17:13 
@Computer_Guy_2007
 
An interesting question - interesting since I have been looking rather deeply into responsiveness issues - using my unpublished version as a base. I suspect that the lessons are applicable to the published version. I assume you are using the published version - right? Unfortunately, I am not at all sure that it would be feasible to apply my optimizations to the published version.
 
That being said, the details of your problem and how it relates to my recent work may well end up providing an answer. Since that correspondence is apt to be lengthy and of no particular relevance to others reading this forum, I propose taking this discussion off line and will send you a separate email to get that started.
 
Just to get the problem defined, let me see if I understand your question. Let me know if I do not get the relationships right.
1. The Folders are on a SBS Server 2003 for all cases?
2. The app runs acceptably fast on a Win XP Client?
3. The app runs acceptably fast on a Win 7 Home edition Client?
4. The app runs unacceptably slow on a Win 7 Pro Client?
 
The obvious question is: What is different between the Win7 Pro and Win7 Home systems - specifically what are the Security and Permission related aspects of the two. If you had said that XP ran fine but both Win7 Clients ran slow then I would say that you are seeing a known problem - Vista/Win7 require much more Security negotiations with the Server and, if I remember correctly, the FileInfo and DirectoryInfo .Net System.IO classes take wildly different approaches depending on OS versions.
To be continued,
Jim

GeneralRe: Any idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberComputer_Guy_20072 Feb '12 - 18:25 
Hi Jim,
Thank you for your reply. I do appreciate it.
To answer your questions:
 
The folders are on a SBS Server 2003 accessed by at least 8 client computers, all are XP pro except for the 2 new Win7 Pro machines that are slow. The app runs fast on XP Pro client but very slow on Win7 Pro client.
 
The testing machines are XP Pro, Vista Business and Win7 Home are each stand alone and not connected to the network. Actually they are at a different location. The app works reasonably fast on all of them.
 
Reading you reply gives me something to look into. I hope I don't have to re-write and re-compile to accomodate the FileInfo and DirectoryInfo changes.
 
I wonder if regasm and regsvr has anything to do with it.
 
I look forward to hear your input.
Regards
Mario
GeneralRe: Any idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberJim Parsells2 Feb '12 - 19:05 
Mario,
There is a very big difference between Folders on a Network machine versus Folders stored locally. That is to say that tests using Local Folders will probably not come close to approximating the elapsed time of Folders on a Network Server.
 
Have you looked at the Forum discussion "Network Location with 32,000 directories"? It is just a few items below this current item. The application you have described has features that are alarmingly close to the app described there.It is quite possible that the solution given in that thread is directly applicable to your code.
 
regasm and regsvr have nothing to do with this.
 
Sad, but true - the major optimizations that I have found are in the Demo Forms, not the ExpTreeLib dll. Optimization changes to ExpTreeLib were mostly minor done to support optimization of the Form code. That means that it almost certain that you will have to change the app - possibly only one line of code, but still a change.
 
Let me how this turns out.
Jim

QuestionRe: Any idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberComputer_Guy_200712 Apr '12 - 18:04 
Hi Jim,
 
Time for me to get back to fixing this problem. I did review the previous posting you suggested. It is as you said 'alarmingly close' in functionality.
 
I wonder if you would take a look at my code below, anything catches you eye? Any suggestion would be appreciated.
 
Thanks
Mario
 

<pre>Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Threading
Imports System.Windows.Forms
Imports ExpTreeLib.CShItem
Imports ExpTreeLib.ShellDll
Imports ExpTreeLib.SystemImageListManager
 

<DefaultProperty("StartUpDirectory"), DefaultEvent("StartUpDirectoryChanged")> _
Public Class ExpTree
    Inherits System.Windows.Forms.UserControl
    Private Root As TreeNode
    Public Event StartUpDirectoryChanged(ByVal newVal As StartDir)
    Public Event ExpTreeNodeSelected(ByVal SelPath As String, ByVal Item As CShItem)
    Private EnableEventPost As Boolean = True 'flag to supress ExpTreeNodeSelected raising during refresh and 
    Private WithEvents DragDropHandler As TVDragWrapper
    Private m_showHiddenFolders As Boolean = True
 
#Region " Windows Form Designer generated code "
 
    Public Sub New()
        MyBase.New()
 
        'This call is required by the Windows Form Designer.
        InitializeComponent()
 
        'Add any initialization after the InitializeComponent() call
 
        'setting the imagelist here allows many good things to happen, but
        ' also one bad thing -- the "tooltip" like display of selectednode.text
        ' is made invisible.  This remains a problem to be solved.
        SystemImageListManager.SetTreeViewImageList(tv1, False)
 
        AddHandler StartUpDirectoryChanged, AddressOf OnStartUpDirectoryChanged
 
        OnStartUpDirectoryChanged(m_StartUpDirectory)
 
        If tv1.IsHandleCreated Then
            If Me.AllowDrop Then
                If Application.OleRequired = Threading.ApartmentState.STA Then
                    DragDropHandler = New TVDragWrapper(tv1)
                    Dim res As Integer
                    res = RegisterDragDrop(tv1.Handle, DragDropHandler)
                    If Not (res = 0) Or (res = -2147221247) Then
                        Marshal.ThrowExceptionForHR(res)
                        Throw New Exception("Failed to Register DragDrop for " & Me.Name)
                    End If
                Else
                    Throw New ThreadStateException("ThreadMustBeSTA")
                End If
            End If
        End If
 

    End Sub
    'ExpTree overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub
 
    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer
 
    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
 
    Friend WithEvents tv1 As System.Windows.Forms.TreeView
 
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.tv1 = New System.Windows.Forms.TreeView()
        Me.SuspendLayout()
        '
        'tv1
        '
        Me.tv1.Dock = System.Windows.Forms.DockStyle.Fill
        Me.tv1.HideSelection = False
        Me.tv1.ImageIndex = -1
        Me.tv1.Name = "tv1"
        Me.tv1.SelectedImageIndex = -1
        Me.tv1.ShowRootLines = False
        Me.tv1.Size = New System.Drawing.Size(200, 264)
        Me.tv1.TabIndex = 0
        '
        'ExpTree
        '
        Me.AllowDrop = True
        Me.Controls.AddRange(New System.Windows.Forms.Control() {Me.tv1})
        Me.Name = "ExpTree"
        Me.Size = New System.Drawing.Size(200, 264)
        Me.ResumeLayout(False)
 
    End Sub
 
#End Region
 
#Region "   Public Properties"
 
#Region "       RootItem"
    '<Summary>
    ' RootItem is a Run-Time only Property
    ' Setting this Item via an External call results in
    '  re-setting the entire tree to be rooted in the 
    '  input CShItem
    ' The new CShItem must be a valid CShItem of some kind
    '  of Folder (File Folder or System Folder)
    ' Attempts to set it using a non-Folder CShItem are ignored
    '</Summary>
    <Browsable(False)> _
    Public Property RootItem() As CShItem
        Get
            Return Root.Tag
        End Get
        Set(ByVal Value As CShItem)
            If Value.IsFolder Then
                If Not IsNothing(Root) Then                 
                    ClearTree()
                End If
                Root = New TreeNode(Value.DisplayName)
                BuildTree(Value.GetDirectories())
                Root.ImageIndex = SystemImageListManager.GetIconIndex(Value, False)
                Root.SelectedImageIndex = Root.ImageIndex
                Root.Tag = Value
                tv1.Nodes.Add(Root)
                Root.Expand()
                tv1.SelectedNode = Root
            End If
        End Set
    End Property
#End Region
 
#Region "       SelectedItem"
    <Browsable(False)> _
    Public ReadOnly Property SelectedItem() As CShItem
        Get
            If Not IsNothing(tv1.SelectedNode) Then
                Return tv1.SelectedNode.Tag
            Else
                Return Nothing
            End If
        End Get
    End Property
#End Region
 
#Region "       ShowHidden"
    <Category("Options"), _
    Description("Show Hidden Directories."), _
    DefaultValue(True), Browsable(True)> _
    Public Property ShowHiddenFolders() As Boolean
        Get
            Return m_showHiddenFolders
        End Get
        Set(ByVal Value As Boolean)
            m_showHiddenFolders = Value
        End Set
    End Property
#End Region
 
#Region "       ShowRootLines"
    <Category("Options"), _
    Description("Allow Collapse of Root Item."), _
    DefaultValue(True), Browsable(True)> _
    Public Property ShowRootLines() As Boolean
        Get
            Return tv1.ShowRootLines
        End Get
        Set(ByVal Value As Boolean)
            If Not (Value = tv1.ShowRootLines) Then
                tv1.ShowRootLines = Value
                tv1.Refresh()
            End If
        End Set
    End Property
#End Region
 
#Region "       StartupDir"
 
    Public Enum StartDir As Integer
        Desktop = &H0
        Programs = &H2
        Controls = &H3
        Printers = &H4
        Personal = &H5
        Favorites = &H6
        Startup = &H7
        Recent = &H8
        SendTo = &H9
        StartMenu = &HB
        MyDocuments = &HC
        'MyMusic = &HD
        'MyVideo = &HE
        DesktopDirectory = &H10
        MyComputer = &H11
        My_Network_Places = &H12
        'NETHOOD = &H13
        'FONTS = &H14
        ApplicatationData = &H1A
        'PRINTHOOD = &H1B
        Internet_Cache = &H20
        Cookies = &H21
        History = &H22
        Windows = &H24
        System = &H25
        Program_Files = &H26
        MyPictures = &H27
        Profile = &H28
        Systemx86 = &H29
        AdminTools = &H30
    End Enum
 
    Private m_StartUpDirectory As StartDir = StartDir.Desktop
 
    <Category("Options"), _
     Description("Sets the Initial Directory of the Tree"), _
     DefaultValue(StartDir.Desktop), Browsable(True)> _
    Public Property StartUpDirectory() As StartDir
        Get
            Return m_StartUpDirectory
        End Get
        Set(ByVal Value As StartDir)
            If Array.IndexOf([Enum].GetValues(Value.GetType), Value) >= 0 Then
                m_StartUpDirectory = Value
                RaiseEvent StartUpDirectoryChanged(Value)
            Else
                Throw New ApplicationException("Invalid Initial StartUpDirectory")
            End If
        End Set
    End Property
#End Region
 
#End Region
 
#Region "   Public Methods"
 
#Region "       RefreshTree"
    '''<Summary>RefreshTree Method thanks to Calum McLellan</Summary>
    <Description("Refresh the Tree and all nodes through the currently selected item")> _
    Public Sub RefreshTree(Optional ByVal rootCSI As CShItem = Nothing)
        'Modified to use ExpandANode(CShItem) rather than ExpandANode(path)
        'Set refresh variable for BeforeExpand method
        EnableEventPost = False
        'Begin Calum's change -- With some modification
        Dim Selnode As TreeNode
        If IsNothing(Me.tv1.SelectedNode) Then
            Selnode = Me.Root
        Else
            Selnode = Me.tv1.SelectedNode
        End If
        'End Calum's change
        Try
            Me.tv1.BeginUpdate()
            Dim SelCSI As CShItem = Selnode.Tag
            'Set root node
            If IsNothing(rootCSI) Then
                Me.RootItem = Me.RootItem
            Else
                Me.RootItem = rootCSI
            End If
            'Try to expand the node
            If Not Me.ExpandANode(SelCSI) Then
                Dim nodeList As New ArrayList()
                While Not IsNothing(Selnode.Parent)
                    nodeList.Add(Selnode.Parent)
                    Selnode = Selnode.Parent
                End While
 
                For Each Selnode In nodeList
                    If Me.ExpandANode(CType(Selnode.Tag, CShItem)) Then Exit For
                Next
            End If
            'Reset refresh variable for BeforeExpand method
        Finally
            Me.tv1.EndUpdate()
        End Try
        EnableEventPost = True
        'We suppressed EventPosting during refresh, so give it one now
        tv1_AfterSelect(Me, New TreeViewEventArgs(tv1.SelectedNode))
    End Sub
#End Region
 
#Region "       ExpandANode"
    Public Function ExpandANode(ByVal newPath As String) As Boolean
        ExpandANode = False     'assume failure
        Dim newItem As CShItem
        Try
            newItem = GetCShItem(newPath)
            If newItem Is Nothing Then Exit Function
            If Not newItem.IsFolder Then Exit Function
        Catch
            Exit Function
        End Try
        Return ExpandANode(newItem)
    End Function
 
    Public Function ExpandANode(ByVal newItem As CShItem) As Boolean
        ExpandANode = False     'assume failure
        Dim baseNode As TreeNode = Root
        tv1.BeginUpdate()
        baseNode.Expand() 'Ensure base is filled in
        'do the drill down -- Node to expand must be included in tree
        Dim testNode As TreeNode
        Dim lim As Integer = CShItem.PidlCount(newItem.PIDL) - CShItem.PidlCount(baseNode.Tag.pidl)
        'TODO: Test ExpandARow again on XP to ensure that the CP problem ix fixed
        Do While lim > 0
            For Each testNode In baseNode.Nodes
                If CShItem.IsAncestorOf(testNode.Tag, newItem, False) Then
                    baseNode = testNode
                    RefreshNode(baseNode)   'ensure up-to-date
                    baseNode.Expand()
                    lim -= 1
                    GoTo NEXLEV
                End If
            Next
            GoTo XIT     'on falling thru For, we can't find it, so get out
NEXLEV: Loop
        'after falling thru here, we have found & expanded the node
        Me.tv1.HideSelection = False
        Me.Select()
        Me.tv1.SelectedNode = baseNode
        ExpandANode = True
XIT:    tv1.EndUpdate()
    End Function
#End Region
 
#End Region
 
#Region "   Initial Dir Set Handler"
 
    Private Sub OnStartUpDirectoryChanged(ByVal newVal As StartDir)
        If Not IsNothing(Root) Then
            ClearTree()
        End If
        Dim special As CShItem
        special = GetCShItem(CType(Val(m_StartUpDirectory), ShellDll.CSIDL))
        Root = New TreeNode(special.DisplayName)
        Root.ImageIndex = SystemImageListManager.GetIconIndex(special, False)
        Root.SelectedImageIndex = Root.ImageIndex
        Root.Tag = special
        BuildTree(special.GetDirectories())
        tv1.Nodes.Add(Root)
        Root.Expand()
    End Sub
 
    Private Sub BuildTree(ByVal L1 As ArrayList)
        L1.Sort()
        Dim CSI As CShItem
        For Each CSI In L1
            If Not (CSI.IsHidden And Not m_showHiddenFolders) Then
                Root.Nodes.Add(MakeNode(CSI))
            End If
        Next
    End Sub
 
    Private Function MakeNode(ByVal item As CShItem) As TreeNode
        Dim newNode As New TreeNode(item.DisplayName)
        newNode.Tag = item
        newNode.ImageIndex = SystemImageListManager.GetIconIndex(item, False)
        newNode.SelectedImageIndex = SystemImageListManager.GetIconIndex(item, True)
        'The following code, from Calum implements the following logic
        ' Allow/disallow the showing of Hidden folders based on ShowHidden Propert
        ' For Removable disks, always show + (allow expansion) - avoids floppy access
        ' For all others, add + based on HasSubFolders
        '  Except - If showing Hidden dirs, do extra check to  allow for
        '  the case of all hidden items in the Dir which will cause
        '  HasSubFolders to be always left unset
        If item.IsRemovable Then             'Calum's fix to hidden file fix
            newNode.Nodes.Add(New TreeNode(" : "))
        ElseIf item.HasSubFolders Then
            newNode.Nodes.Add(New TreeNode(" : "))
            'Begin Calum's change so Hidden dirs with all hidden content are expandable
        ElseIf item.GetDirectories.Count > 0 Then   'Added Code
            newNode.Nodes.Add(New TreeNode(" : "))  'Added Code
            'End Calum's change
        End If
        Return newNode
    End Function
 
    Private Sub ClearTree()
        tv1.Nodes.Clear()
        Root = Nothing
    End Sub
#End Region
 
#Region "   TreeView BeforeExpand Event"
 
    Private Sub tv1_BeforeExpand(ByVal sender As Object, ByVal e As 
 
System.Windows.Forms.TreeViewCancelEventArgs) Handles tv1.BeforeExpand
        Dim oldCursor As Cursor = Cursor
        Cursor = Cursors.WaitCursor
        If e.Node.Nodes.Count = 1 AndAlso e.Node.Nodes(0).Text.Equals(" : ") Then
            'Debug.WriteLine("Expanding -- " & e.Node.Text)
            e.Node.Nodes.Clear()
            Dim CSI As CShItem = e.Node.Tag
            Dim D As ArrayList = CSI.GetDirectories()
 
            If D.Count > 0 Then
                D.Sort()    'uses the class comparer
                Dim item As CShItem
                For Each item In D
                    If Not (item.IsHidden And Not m_showHiddenFolders) Then
                        e.Node.Nodes.Add(MakeNode(item))
                    End If
                Next
            End If
        Else    'Ensure content is accurate
            RefreshNode(e.Node)
        End If
        Cursor = oldCursor
    End Sub
#End Region
 
#Region "   TreeView AfterSelect Event"
    Private Sub tv1_AfterSelect(ByVal sender As System.Object, ByVal e As 
 
System.Windows.Forms.TreeViewEventArgs) Handles tv1.AfterSelect
        Dim node As TreeNode = e.Node
        Dim CSI As CShItem = e.Node.Tag
        If CSI Is Root.Tag AndAlso Not tv1.ShowRootLines Then
            With tv1
                Try
                    .BeginUpdate()
                    .ShowRootLines = True
                    RefreshNode(node)
                    .ShowRootLines = False
                Finally
                    .EndUpdate()
                End Try
            End With
        Else
            RefreshNode(node)
        End If
        If EnableEventPost Then 'turned off during RefreshTree
            If CSI.Path.StartsWith(":") Then
                RaiseEvent ExpTreeNodeSelected(CSI.DisplayName, CSI)
            Else
                RaiseEvent ExpTreeNodeSelected(CSI.Path, CSI)
            End If
        End If
    End Sub
#End Region
 
#Region "   RefreshNode Sub"
 
    Private Sub RefreshNode(ByVal thisRoot As TreeNode)
        'Debug.WriteLine("In RefreshNode: Node = " & thisRoot.Tag.path & " -- " & thisRoot.Tag.displayname)
        If Not (thisRoot.Nodes.Count = 1 AndAlso thisRoot.Nodes(0).Text.Equals(" : ")) Then
            Dim thisItem As CShItem = thisRoot.Tag
            If thisItem.RefreshDirectories Then   'RefreshDirectories True = the contained list of Directories 
 
has changed
                Dim curDirs As ArrayList = thisItem.GetDirectories(False) 'suppress 2nd refresh
                Dim delNodes As New ArrayList()
                Dim node As TreeNode
                For Each node In thisRoot.Nodes 'this is the old node contents
                    Dim i As Integer
                    For i = 0 To curDirs.Count - 1
                        If CType(curDirs(i), CShItem).Equals(node.Tag) Then
                            curDirs.RemoveAt(i)   'found it, don't compare again
                            GoTo NXTOLD
                        End If
                    Next
                    'fall thru = node no longer here
                    delNodes.Add(node)
NXTOLD:         Next
                If delNodes.Count + curDirs.Count > 0 Then  'had changes
                    Try
                        tv1.BeginUpdate()
                        For Each node In delNodes 'dir not here anymore, delete node
                            thisRoot.Nodes.Remove(node)
                        Next
                        'any CShItems remaining in curDirs is a new dir under thisRoot
                        Dim csi As CShItem
                        For Each csi In curDirs
                            If Not (csi.IsHidden And Not m_showHiddenFolders) Then
                                thisRoot.Nodes.Add(MakeNode(csi))
                            End If
                        Next
                        'we only need to resort if we added
                        'sort is based on CShItem in .Tag
                        If curDirs.Count > 0 Then
                            Dim tmpA(thisRoot.Nodes.Count - 1) As TreeNode
                            thisRoot.Nodes.CopyTo(tmpA, 0)
                            Array.Sort(tmpA, New TagComparer())
                            thisRoot.Nodes.Clear()
                            thisRoot.Nodes.AddRange(tmpA)
                        End If
                    Catch ex As Exception
                        Debug.WriteLine("Error in RefreshNode -- " & ex.ToString _
                                        & vbCrLf & ex.StackTrace)
                    Finally
                        tv1.EndUpdate()
                    End Try
                End If
            End If
        End If
        'Debug.WriteLine("Exited RefreshNode")
    End Sub
 
#End Region
 
#Region "   TreeView VisibleChanged Event"
    '''<Summary>When a form containing this control is Hidden and then re-Shown,
    ''' the association to the SystemImageList is lost.  Also lost is the
    ''' Expanded state of the various TreeNodes. 
    ''' The VisibleChanged Event occurs when the form is re-shown (and other times
    '''  as well).  
    ''' We re-establish the SystemImageList as the ImageList for the TreeView and
    ''' restore at least some of the Expansion.</Summary> 
    Private Sub tv1_VisibleChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles 
 
tv1.VisibleChanged
        If tv1.Visible Then
            SystemImageListManager.SetTreeViewImageList(tv1, False)
            If Not Root Is Nothing Then
                Root.Expand()
                If Not IsNothing(tv1.SelectedNode) Then
                    tv1.SelectedNode.Expand()
                Else
                    tv1.SelectedNode = Me.Root
                End If
            End If
        End If
    End Sub
#End Region
 
#Region "   TreeView BeforeCollapse Event"
    '''<Summary>Should never occur since if the condition tested for is True,
    ''' the user should never be able to Collapse the node. However, it is
    ''' theoretically possible for the code to request a collapse of this node
    ''' If it occurs, cancel it</Summary>
    Private Sub tv1_BeforeCollapse(ByVal sender As Object, ByVal e As 
 
System.Windows.Forms.TreeViewCancelEventArgs) Handles tv1.BeforeCollapse
        If Not tv1.ShowRootLines AndAlso e.Node Is Root Then
            e.Cancel = True
        End If
    End Sub
#End Region
 
#Region "   tv1_HandleDestroyed"
    Private Sub tv1_HandleDestroyed(ByVal sender As Object, ByVal e As EventArgs) Handles tv1.HandleDestroyed
        'Debug.WriteLine("in handle destroyed")
        If Me.AllowDrop Then
            Dim res As Integer
            res = RevokeDragDrop(tv1.Handle)
            If res <> 0 Then
                Debug.WriteLine("RevokeDragDrop returned " & res)
            End If
            'Else
            '    Debug.WriteLine("HandleDestroyed with allowdrop false")
        End If
    End Sub
#End Region
 
#Region "   FindAncestorNode"
    '''<Summary>Given a CShItem, find the TreeNode that belongs to the
    ''' equivalent (matching PIDL) CShItem's most immediate surviving ancestor.
    '''  Note: referential comparison might not work since there is no guarantee
    ''' that the exact same CShItem is stored in the tree.</Summary>
    '''<returns> Me.Root if not found, otherwise the Treenode whose .Tag is
    ''' equivalent to the input CShItem's most immediate surviving ancestor </returns>
    Private Function FindAncestorNode(ByVal CSI As CShItem) As TreeNode
        FindAncestorNode = Nothing
        If Not CSI.IsFolder Then Exit Function 'only folders in tree
        Dim baseNode As TreeNode = Root
        'Dim cp As cPidl = CSI.clsPidl     'the cPidl rep of the PIDL to be found
        Dim testNode As TreeNode
        Dim lim As Integer = PidlCount(CSI.PIDL) - PidlCount(baseNode.Tag.pidl)
        Do While lim > 1
            For Each testNode In baseNode.Nodes
                If CShItem.IsAncestorOf(testNode.Tag, CSI, False) Then
                    baseNode = testNode
                    baseNode.Expand()
                    lim -= 1
                    GoTo NEXTLEV
                End If
            Next
            'CSI's Ancestor may have moved or been deleted, return the last one
            ' found (if none, will return Me.Root)
            Return baseNode
NEXTLEV: Loop
        'on fall thru, we have it
        Return baseNode
    End Function
#End Region
 
#Region "   Drag/Drop From Tree Processing"
 
    Private Sub tv1_ItemDrag(ByVal sender As Object, ByVal e As System.Windows.Forms.ItemDragEventArgs) 
 
Handles tv1.ItemDrag
        'Primary (internal) data type
        Dim toDrag As New ArrayList()
        Dim csi As CShItem = CType(e.Item, TreeNode).Tag
        toDrag.Add(csi)
        'also need Shell IDList Array
        Dim MS As System.IO.MemoryStream
        MS = CProcDataObject.MakeShellIDArray(toDrag)
        'Fairly universal data type (must be an array)
        Dim strD(0) As String
        strD(0) = csi.Path
        'Build data to drag
        Dim dataObj As New DataObject()
        With dataObj
            .SetData(toDrag)
            If Not IsNothing(MS) Then
                .SetData("Shell IDList Array", True, MS)
            End If
            .SetData("FileDrop", True, strD)
        End With
        'Do drag, allowing Copy and Move
        Dim ddeff As DragDropEffects
        ddeff = tv1.DoDragDrop(dataObj, DragDropEffects.Copy Or DragDropEffects.Move)
        'the following line commented out, since we can't depend on ddeff
        'If ddeff = DragDropEffects.None Then Exit Sub 'nothing happened
        RefreshNode(FindAncestorNode(csi))
    End Sub
 
#End Region
 
#Region "   DragWrapper Event Handling"
 
    ' dropNode is the TreeNode that most recently was DraggedOver or
    '    Dropped onto.  
    Private dropNode As TreeNode
 
    'expandNodeTimer is used to expand a node that is hovered over, with a delay
    Private WithEvents expandNodeTimer As New System.Windows.Forms.Timer()
 
#Region "       expandNodeTimer_Tick"
    Private Sub expandNodeTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) _
       Handles expandNodeTimer.Tick
        expandNodeTimer.Stop()
        If Not IsNothing(dropNode) Then
            RemoveHandler DragDropHandler.ShDragOver, AddressOf DragWrapper_ShDragOver
            Try
                tv1.BeginUpdate()
                dropNode.Expand()
                dropNode.EnsureVisible()
            Finally
                tv1.EndUpdate()
            End Try
            AddHandler DragDropHandler.ShDragOver, AddressOf DragWrapper_ShDragOver
        End If
    End Sub
#End Region
 
    '''<Summary>ShDragEnter does nothing. It is here for debug tracking</Summary>
    Private Sub DragWrapper_ShDragEnter(ByVal Draglist As ArrayList, _
                                        ByVal pDataObj As IntPtr, _
                                        ByVal grfKeyState As Integer, _
                                        ByVal pdwEffect As Integer) _
                                Handles DragDropHandler.ShDragEnter
        'Debug.WriteLine("Enter ExpTree ShDragEnter. PdwEffect = " & pdwEffect)
    End Sub
 
    '''<Summary>Drag has left the control. Cleanup what we have to</Summary>
    Private Sub DragWrapper_ShDragLeave() Handles DragDropHandler.ShDragLeave
        expandNodeTimer.Stop()    'shut off the dragging over nodes timer
        'Debug.WriteLine("Enter ExpTree ShDragLeave")
        If Not IsNothing(dropNode) Then
            ResetTreeviewNodeColor(dropNode)
        End If
        dropNode = Nothing
    End Sub
 
    '''<Summary>ShDragOver manages the appearance of the TreeView.  Management of
    ''' the underlying FolderItem is done in DragWrapper
    ''' Credit to Cory Smith for TreeView colorizing technique and code,
    ''' at http://addressof.com/blog/archive/2004/10/01/955.aspx
    ''' Node expansion based on expandNodeTimer added by me.
    '''</Summary>
    Private Sub DragWrapper_ShDragOver(ByVal Node As Object, _
                                ByVal pt As System.Drawing.Point, _
                                ByVal grfKeyState As Integer, _
                                ByVal pdwEffect As Integer) _
                                Handles DragDropHandler.ShDragOver
        'Debug.WriteLine("Enter ExpTree ShDragOver. PdwEffect = " & pdwEffect)
        'Debug.WriteLine(vbTab & "Over node: " & CType(Node, TreeNode).Text)
 
        If IsNothing(Node) Then  'clean up node stuff & fix color. Leave Draginfo alone-cleaned up on 
 
DragLeave
            expandNodeTimer.Stop()
            If Not dropNode Is Nothing Then
                ResetTreeviewNodeColor(dropNode)
                dropNode = Nothing
            End If
        Else  'Drag is Over a node - fix color & DragDropEffects
            If Node Is dropNode Then
                Exit Sub    'we've already done it all
            End If
 
            expandNodeTimer.Stop() 'not over previous node anymore
            Try
                tv1.BeginUpdate()
                Dim delta As Integer = tv1.Height - pt.Y
                If delta < tv1.Height / 2 And delta > 0 Then
                    If Not IsNothing(Node) AndAlso Not (Node.NextVisibleNode Is Nothing) Then
                        Node.NextVisibleNode.EnsureVisible()
                        ' Thread.Sleep(250)  'slow down a bit
                    End If
                End If
                If delta > tv1.Height / 2 And delta < tv1.Height Then
                    If Not IsNothing(Node) AndAlso Not (Node.PrevVisibleNode Is Nothing) Then
                        Node.PrevVisibleNode.EnsureVisible()
                        ' Thread.Sleep(250)   'slow down a bit
                    End If
                End If
                If Not Node.BackColor.Equals(SystemColors.Highlight) Then
                    ResetTreeviewNodeColor(tv1.Nodes(0))
                    Node.BackColor = SystemColors.Highlight
                    Node.ForeColor = SystemColors.HighlightText
                End If
            Finally
                tv1.EndUpdate()
            End Try
            dropNode = Node     'dropNode is the Saved Global version of Node
            If Not dropNode.IsExpanded Then
                expandNodeTimer.Interval = 1200
                expandNodeTimer.Start()
            End If
        End If
    End Sub
 
    Private Sub DragWrapper_ShDragDrop(ByVal DragList As ArrayList, _
                                ByVal Node As Object, _
                                ByVal grfKeyState As Integer, _
                                ByVal pdwEffect As Integer) Handles DragDropHandler.ShDragDrop
        expandNodeTimer.Stop()
        'Debug.WriteLine("Enter ExpTree ShDragDrop. PdwEffect = " & pdwEffect)
        'Debug.WriteLine(vbTab & "Over node: " & CType(Node, TreeNode).Text)
 
        If Not IsNothing(dropNode) Then
            ResetTreeviewNodeColor(dropNode)
        Else
            ResetTreeviewNodeColor(tv1.Nodes(0))
        End If
        ' If Directories were Moved, we must find and update the DragSource TreeNodes
        '  of course, it is possible that the Drag was external to the App and 
        '  the DragSource TreeNode might not exist in the Tree
        'All of this is somewhat chancy since we can't count on pdwEffect or
        '  on a Move having actually started, let alone finished
        Dim CSI As CShItem      'that is what is in DragList
        For Each CSI In DragList
            If CSI.IsFolder Then    'only care about Folders
                RefreshNode(FindAncestorNode(CSI))
            End If
        Next
        If tv1.SelectedNode Is dropNode Then   'Fake a reselect
            Dim e As New System.Windows.Forms.TreeViewEventArgs(tv1.SelectedNode, TreeViewAction.Unknown)
            tv1_AfterSelect(tv1, e)      'will do a RefreshNode and raise AfterNodeSelect Event
        Else
            RefreshNode(dropNode)        'Otherwise, just refresh the Target
            If pdwEffect <> DragDropEffects.Copy AndAlso pdwEffect <> DragDropEffects.Link Then
                'it may have been a move. if so need to do an AfterSelect on the DragSource if it is 
 
SelectedNode
                If DragList.Count > 0 Then     'can't happen but check
                    If Not IsNothing(tv1.SelectedNode) Then     'ditto
                        Dim csiSel As CShItem = tv1.SelectedNode.Tag
                        Dim csiSource As CShItem = DragList(0)  'assume all from same dir
                        If CShItem.IsAncestorOf(csiSel, csiSource) Then 'also true for equality
                            Dim e As New System.Windows.Forms.TreeViewEventArgs(tv1.SelectedNode, 
 
TreeViewAction.Unknown)
                            tv1_AfterSelect(tv1, e)      'will do a RefreshNode and raise AfterNodeSelect 
 
Event
                        End If
                    End If
                End If
            End If
        End If
        dropNode = Nothing
        'Debug.WriteLine("Leaving ExpTree ShDragDrop")
    End Sub
 
    Private Sub ResetTreeviewNodeColor(ByVal node As TreeNode)
        If Not node.BackColor.Equals(Color.Empty) Then
            node.BackColor = Color.Empty
            node.ForeColor = Color.Empty
        End If
        If Not node.FirstNode Is Nothing AndAlso node.IsExpanded Then
            Dim child As TreeNode
            For Each child In node.Nodes
                If Not child.BackColor.Equals(Color.Empty) Then
                    child.BackColor = Color.Empty
                    child.ForeColor = Color.Empty
                End If
                If Not child.FirstNode Is Nothing AndAlso child.IsExpanded Then
                    ResetTreeviewNodeColor(child)
                End If
            Next
        End If
    End Sub
#End Region
 
End Class

AnswerRe: Any idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberJim Parsells13 Apr '12 - 7:46 
Mario,
 
I have sent you email through this Forum's email reply feature. Have you received it??
Jim

GeneralRe: Any idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberComputer_Guy_200713 Apr '12 - 10:58 
Hi Jim,
No I did not receive your email. you can email me directly mario.hmeidani@gmail.com
Thanks
GeneralRe: Any idea/guess why ExpTreeLib.dll would take 5+ minutes to load on a new Win7 machines?memberJim Parsells17 Apr '12 - 10:45 
Mario,
 
I sent you a couple of items (version 2.12 and a revision to it) at the gmail address. Did you get them?
I have a small correction to that version, but won't send that until I hear back from you to confirm that you are getting my mail.
 
After re-reading your initial question, I am pretty sure that my original answer is correct and will probably solve your problem with no further action on your part.
However, my version 2.12 will give better overall performance in the scenario you describe and it is a drop-in replacement for version 2.11.
 
You may contact me directly at RustBucket37@gmail.com
 
PS: the code you posted is identical to my version 2.11 code.
Jim

QuestionDragdrop between my treeview to your controlsmembermozac8320 Nov '11 - 22:13 
Hi there, good explorer tree controls u have there and u know what, im envy your program! I tried to fully understand the codes but im still new to this shell programming. What im trying to achieve is to drag and drop from treeview node(hardcoded by myself) to your control successfully. I mean like node("text.txt") will be copied to your control as a node with the same name "text.txt". I already tried TreeView1_MouseDown, treeview1_dragover and Private Sub ExpTree3_DragDrop events but to no avail. Please kindly guide me how to achieve this and i'll appreciate for your help tq.
AnswerRe: Dragdrop between my treeview to your controlsmemberJim Parsells21 Nov '11 - 5:17 
Assuming you are using the published code and the Demo Form frmDragDrop, then it should work if you have properly constructed and passed a DataObject. Note: the Demo Form frmExplorerLike will not accept Drops. Note also: the DataObject that you construct must have, at least, FileDrop formatted data. See:
Private Sub tv1_ItemDrag in ExpTree.vb or Private Sub lv1_ItemDrag in frmDragDrop.vb for an example of creating a proper DataObject. The minimum data format that you have to provide is FileDrop formatted data.
 
The ExpTree will only show Dropped Folders, not Files. The Drop of a Dragged File will result in copy or move of the file to the Node, but that file will not show up as a node in ExpTree.
Jim

QuestionCShitem Crashes sometimesmemberamolpbhavsar1 Sep '11 - 1:21 
Hello,
the following is the stracktrace for CShitem crash
 
ErrorDescriptionForBug: System.Runtime.InteropServices.COMException, Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))
ErrorStackTrace: at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
at ExpTreeLib.CShItem..ctor(IShellFolder folder, IntPtr pidl, CShItem parent)
at ExpTreeLib.CShItem.GetContents(SHCONTF flags, Boolean IntPtrOnly)
at ExpTreeLib.CShItem.RefreshDirectories()
at ExpTreeLib.CShItem.GetDirectories(Boolean doRefresh)
at ExpTreeLib.CShItem.FindCShItem(IntPtr ptr)
at ExpTreeLib.CShItem.GetCShItem(String path)
at ExpTreeLib.ExpTree.ExpandANode(String newPath)
AnswerRe: CShitem Crashes sometimesmemberJim Parsells1 Sep '11 - 10:32 
I need a bit more information:
  1. Which version are you using - the published or the unpublished version?
  2. What is your environment - which OS, 32 or 64 bit - which version of the Framework, which version Visual Studio?
  3. Have you modified my code? Have you added other .dlls to your project?
  4. Getting a StackTrace with line numbers would be better.
  5. Is there anything different about the Folder you are Selecting?
  6. Does this always happen when browsing to a particular Folder? Can you browse to that Folder without this problem occurring?
  7. Is ASUS backup installed on the PC? Have you installed the fix for ASUS discussed in the forum?
  8. Is there any other 3rd party software on the PC that created a special Folder on installation?
  9. Have you tried testing for that error and ignoring it when it occurs rather than
    Marshal.ThrowExceptionForHR(HR)?
    If so, what happens?

GeneralRe: CShitem Crashes sometimesmemberamolpbhavsar4 Sep '11 - 19:15 
The following is some information which may help you.
OS: Microsoft Windows XP Professional [System Type: 32 bit Operating System] Major: 5 Minor: 1 Service Pack: Service Pack 3
Some small modification are made in the code. But it is working properly on other systems and for OS. I have added .dlls in my application.
The line no in stack trace is not available but going through the Stack Trace, it revels that crash is produced in "GetContents" function and while initializing the CSHItem .
The problem is appearing on XP mostly and occurring. Looks like there is no any particular scenario.
Following is some more information
OS: Microsoft Windows XP Professional [System Type: 32 bit Operating System] Major: 5 Minor: 1 Service Pack: Service Pack 3
Processor Details : Intel(R) Core(TM)2 Duo CPU E7400 @ 2.80GHz
Antivirus Found :Display : Microsoft Security Essentials
Framework: v2.0.50727, Service Pack: 2
 
I tried one thing that if the code which is causing the problem if skipped, the folders enumerated in the ExpTreeLib are not showing their children.
 
Let me know, this information is sufficient or not.
Regards,
Amol P Bhavsar
GeneralRe: CShitem Crashes sometimesmemberJim Parsells5 Sep '11 - 19:51 
With that info, I have determined a number of possiblities that have not caused the problem. Unfortunately, this does not tell me what does cause the problem.
 
My first guess is that this occurs on a specific Folder and that Folder is managed by a Shell Extension. If the Shell Extension does not follow all the rules for Shell Extensions, bad things like this can occur. Note that there may be several Folders managed by a Shell Extension so that problems could occur on any of them, if the Shell Extension is not fully implemented or is otherwise deficient. The prime example of this is the ASUS online backup application discussed earlier in this forum. That one manifested itself in a slightly different way, but other ways are possible.
 
From the StackTrace, I have arrived at the conclusion that the exception occurs when the following statement returns an error:
HR = folder.BindToObject(pidl, IntPtr.Zero, IID_IShellFolder, m_Folder)
I assume you arrived at the same conclusion?
 
Given that and the rest of the StackTrace, no matter the actual cause, the fix for this is the same as that given in the ASUS discussion found below (about the 4th page) in this forum. Make one change in the fix --- rather than Catching InvalidCastException, Catch Exception. The Try..Catch blocks occur in two places. Search for the specified lines in CShItem and install or change the Try..Catch block code.
 
This will indeed result in not displaying the contents of the offending Folder. That cannot be helped...You can't get the Content of a bad Folder.
 
Should you develop more information or need help in installing the suggested fix, reply to this message and I will do what I can to solve the problem.
Jim Parsells

GeneralRe: CShitem Crashes sometimesmemberJim Parsells7 Sep '11 - 18:52 
There is one other possible explanation for this error.
In
    Private Sub New(ByVal folder As IShellFolder, ByVal pidl As IntPtr, ByVal parent As CShItem)
Change the following From:
If m_IsFolder Then
        Dim HR As Integer
        HR = folder.BindToObject(pidl, IntPtr.Zero, IID_IShellFolder, m_Folder)
        If HR <> NOERROR Then
            Marshal.ThrowExceptionForHR(HR)
        End If
    End If
End Sub
To:
If m_IsFolder Then
        Dim HR As Integer
        HR = folder.BindToObject(pidl, IntPtr.Zero, IID_IShellFolder, m_Folder)
        If HR >= NOERROR Then   'Changed line
            Marshal.ThrowExceptionForHR(HR)
        End If
    End If
End Sub
In some cases, which I have only seen with a few NetWork Folders on XP, HR is set to a positive value greater than NOERROR when a perfectly valid m_Folder has been returned. In the normal case, this is basically harmless since Marshal.ThrowExceptionForHR will not normally throw an exception for any zero or positive value. However, ThrowException may (the documentation is a bit unclear) throw an exception if IErrorInfo if set for the current thread. In this case, the exception thrown will have nothing to do with the current value of HR and may well refer to some earlier (Handled) error.
 
Since nothing in ExpTreeLib is ever likely to throw a REGDB_E_CLASSNOTREG error, this may be the root of your problem.
 
If this is not the case, then I would ascribe REGDB_E_CLASSNOTREG as being a Shell response to a Folder left by a currently deactivated or imperfectly uninstalled Shell Extension, as discussed in prior forum discussions.
Jim Parsells

QuestionPossible request for the AfterSelect for the treeviewmemberBrenden Kromhout30 Aug '11 - 11:41 
Hey Jim, first can I say thanks so much for creating this, it's been a huge help to me! I was wondering, is it possible to create an option that allows switching between either putting just the files (and folders) inside of the folder you've selected into the listview (Like what it does now), and recursively putting all the files (and folders) both in the folder you've selected and in any and all folders under it into the listview? I know this may be a strange request, but I'm creating a batch renaming program, and it'd be useful for me to have an option to switch between the two things I've said above. I noticed you've defined your own procedures for retrieving files and folders and I didn't want to break things by attempting to circumvent them. Thanks in advance!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 17 May 2012
Article Copyright 2004 by Jim Parsells
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid