How to Create a TreeView File Browser Component in VB.NET – Part 2

This tutorial is part of a series: Part 1 | Part 2TreeView Part 2

In Part 1 of this tutorial series, I presented a way to use the .NET TreeView control as a file browser similar to Windows Explorer in functionality. As we navigate through this TreeView, the BeforeExpand event is handled so that each node is dynamically populated as soon as it gets expanded.

The second part of this tutorial focuses on displaying the native system icons for each file and folder in the TreeView.

Getting Started

We will continue building upon the TreeView File Browser Component from Part 1. Open the project and let’s get started.

Implement SHGetFileInfo()

Add a new Module to your project. It should have the default name of Module1.vb.

Copy/paste the following code into Module1.vb:

Public Declare Auto Function SHGetFileInfo Lib "shell32.dll" (ByVal pszPath As String, ByVal dwFileAttributes As Integer, ByRef psfi As SHFILEINFO, ByVal cbFileInfo As Integer, ByVal uFlags As Integer) As IntPtr

Public Const SHGFI_ICON As Integer = &H100
Public Const SHGFI_SMALLICON As Integer = &H1
Public Const SHGFI_LARGEICON As Integer = &H0
Public Const SHGFI_OPENICON As Integer = &H2

Structure SHFILEINFO
    Public hIcon As IntPtr
    Public iIcon As Integer
    Public dwAttributes As Integer
    <Runtime.InteropServices.MarshalAs(Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=260)> _
    Public szDisplayName As String
    <Runtime.InteropServices.MarshalAs(Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=80)> _
    Public szTypeName As String
End Structure

This is a simple implementation of the Win32 API call SHGetFileInfo, which can do more than just help us retrieve icons from the Windows shell. You can read more about it on the web.

In the code above, we have declared SHGetFileInfo (line 1), a function native to Windows which is stored in it’s own shell32.dll file. By implementing this API call in our code, we can tap into the same function that Windows uses to retrieve the actual system icons for files and folders.

Windows Explorer Icons

A single folder icon consists of several images

SHGetFileInfo needs to know whether we are requesting an icon at all (because we can actually request things other than an icon). It also needs to know whether it is a small icon (16×16), a large icon (full size), as well as whether the icon is in the open state (such as when a folder appears open in Windows Explorer), so we have defined the constants for these (line 3-6).

Furthermore, the SHGetFileInfo needs something to store the result of our request (in this case, the icon), so we must supply it with an SHFILEINFO structure (line 8-16).

Create a wrapper function to make using SHGetFileInfo easier

The important thing to understand about Win32 API calls is that they sometimes do not return values in the exact format we need them in. For example, SHGetFileInfo does not return a simple System.Drawing.Image or System.Drawing.Icon that we can use immediately in .NET code. Therefore, we will have to extract the returned values from the SHFILEINFO structure and convert them into the desired .NET objects. Basically, we need a wrapper function.

The wrapper function should call SHGetFileInfo, extract the icon from the SHFILEINFO structure and convert it to a System.Drawing.Image, then return the System.Drawing.Image. GetShellIconAsImage will be our wrapper function that wraps up the logic which will make this all happen.

Copy/paste the following code into Module1.vb:

' GetShellIconAsImage
Function GetShellIconAsImage(ByVal argPath As String) As Image
    Dim mShellFileInfo As New SHFILEINFO
    mShellFileInfo.szDisplayName = New String(Chr(0), 260)
    mShellFileInfo.szTypeName = New String(Chr(0), 80)
    SHGetFileInfo(argPath, 0, mShellFileInfo, System.Runtime.InteropServices.Marshal.SizeOf(mShellFileInfo), SHGFI_ICON Or SHGFI_SMALLICON)
    ' attempt to create a System.Drawing.Icon from the icon handle that was returned in the SHFILEINFO structure
    Dim mIcon as System.Drawing.Icon
    Dim mImage as System.Drawing.Image
    Try
        mIcon = System.Drawing.Icon.FromHandle(mShellFileInfo.hIcon)
        mImage = mIcon.ToBitmap
    Catch ex As Exception
        ' for some reason the icon could not be converted so create a blank System.Drawing.Image to return instead
        mImage = New System.Drawing.Bitmap(16, 16)
    End Try
    ' return the final System.Drawing.Image
    Return mImage
End Function

Let us understand what is going on in this function.

First, we define the GetShellIconAsImage function (line 2), which accepts a single argument for the path of the file whose icon we wish to retrieve.

We then instantiate a new SHFILEINFO structure (line 3) and fill it with some required data (line 4-5). Basically, we must stuff the szDisplayName and szTypeName properties of the SHFILEINFO structure with blank strings before passing it to SHGetFileInfo, otherwise the structure will be of the wrong size and appear as garbage to SHGetFileInfo. A tad bit strange, but this is common with the Win32 API…

Next we call SHGetFileInfo with several parameters (line 6). You can see that we pass in our SHFILEINFO structure, which will get populated with file information after SHGetFileInfo executes.

Finally, we attempt to convert the icon handle in the SHFILEINFO structure to a System.Drawing.Icon, using the System.Drawing.Icon.FromHandle function (line 11). We then convert the System.Drawing.Icon to a System.Drawing.Image (line 12) because this is what .NET likes most. If this procedure fails, we just return a blank System.Drawing.Image (line 15).

We now have a method of retrieving an icon from the Windows shell as a System.Drawing.Image, but where do we store all of these icons, how do we know which icons to retrieve, and when?

Caching the icons for repeated use and quicker access

If you recall back to part one, nodes are populated on demand, meaning every time a user expands a node we request a new listing from the underlying filesystem. This is great, because as we are adding each child node, we can also retrieve the icon from the Windows shell using our wrapper function GetShellIconAsImage, then apply this icon to the node’s ImageKey property before adding it to the TreeView.

Since the TreeView control supports displaying icons from an ImageList, we are going to cache the icons from the Windows shell and store them in an ImageList that is bound to the TreeView control. This caching process should appear straightforward in just a moment.

Open UserControl1 in Design Mode and add an ImageList control from the ToolBox. It should have the default name of ImageList1.

We now want to bind this ImageList control to our TreeView control. Select the TreeView1 control and change it’s ImageList property to ImageList1. The TreeView is now able to display images from ImageList1.

Add the following code to UserControl1.vb:

Function CacheShellIcon(ByVal argPath As String) As String
    Dim mKey As String = Nothing
    ' determine the icon key for the file/folder specified in argPath
    If IO.Directory.Exists(argPath) = True Then
        mKey = "folder"
    ElseIf IO.File.Exists(argPath) = True Then
        mKey = IO.Path.GetExtension(argPath)
    End If
    ' check if an icon for this key has already been added to the collection
    If ImageList1.Images.ContainsKey(mKey) = False Then
        ImageList1.Images.Add(mKey, GetShellIconAsImage(argPath))
    End If
    Return mKey
End Function

When adding an Image to an ImageList, we can supply a unique key (line 4-8) that can be used to retrieve the Image later. Using this logic, we can store each icon that we cache from the Windows shell in the ImageList using a key equal to the file’s extension (line 11). For example, when the icon gets cached for a Text File, we add it to the ImageList using .txt as the key. The next time we encounter a Text File, we check if the ImageList already contains a .txt key (line 10), if so we skip the file since the icon has already been cached.

At this point, we haven’t actually associated any cached icons with a node in the TreeView. Let’s do that now.

Modify the UserControl1_Load event and include these lines of code:

mRootNode.ImageKey = CacheShellIcon(RootPath)
mRootNode.SelectedImageKey = mRootNode.ImageKey

Modify the TreeView1_BeforeExpand event and include these lines of code:

mDirectoryNode.ImageKey = CacheShellIcon(mDirectory.FullName)
mDirectoryNode.SelectedImageKey = mDirectoryNode.ImageKey

What about the “open folder” icon?

You may have noticed that when you select a folder in Windows Explorer and it becomes highlighted, the icon also changes to one where the folder appears to be “open” rather than closed. Remember earlier when we defined SHGFI_OPENICON? We simply supply this constant in the uFlags parameter of the SHGetFileInfo call in our GetShellIconAsImage method.

Take a look at the original call to SHGetFileInfo:

SHGetFileInfo(IO.Path.GetTempPath, 0, mShellFileInfo, System.Runtime.InteropServices.Marshal.SizeOf(mShellFileInfo), SHGFI_ICON Or SHGFI_SMALLICON)

If you change the uFlags parameter from this:

SHGFI_ICON Or SHGFI_SMALLICON

To this:

SHGFI_ICON Or SHGFI_SMALLICON Or SHGFI_OPENICON

You can retrieve the small open icon. Here’s a simple way to implement this in your current component.

Make a copy of your original GetShellIconAsImage method and name it GetShellOpenIconAsImage. The only thing different about this method is that you want to supply SHGFI_OPENICON in the uFlags parameter as I have described above.

The next thing to do is modify the CacheShellIcon method to include this line after the one that is similar to it:

ImageList1.Images.Add(mKey & "-open", GetShellOpenIconAsImage(argPath))

Displaying Files in the TreeView

Up to this point, we’ve only been displaying folders in the TreeView because this is how Windows Explorer does it. Windows Explorer has a separate panel for displaying the contents of the selected folder. However, let’s not reinvent the wheel. My goal in these tutorials is to show you the code and the logic that makes these ideas work, but it’s your job to apply them to your own projects in your own way.

What we are going to do instead is display the files directly in the TreeView. All we need to do is include a second loop in the TreeView1_BeforeExpand event to populate the files. Just copy/paste the loop block that we used for populating folders, and modify it for files like so:


' add each file from the file system that is a child of the argNode that was passed in
For Each mFile As IO.FileInfo In mNodeDirectory.GetFiles
' declare a TreeNode for this file
Dim mFileNode As New TreeNode
' store the full path to this file in the file TreeNode's Tag property
mFileNode.Tag = mFile.FullName
' set the file TreeNodes's display text
mFileNode.Text = mFile.Name
mFileNode.ImageKey = CacheShellIcon(mFile.FullName)
mFileNode.SelectedImageKey = mFileNode.ImageKey & "-open"
' add this file TreeNode to the TreeNode that is being populated
e.Node.Nodes.Add(mFileNode)
Next

What fun would this tutorial be without being able to double click the files to open them? Handle the TreeView1_NodeMouseDoubleClick event in UserControl1 like so:

Private Sub TreeView1_NodeMouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeNodeMouseClickEventArgs) Handles TreeView1.NodeMouseDoubleClick
  ' only proceed if the node represents a file
  If e.Node.ImageKey = "folder" Then Exit Sub
  If e.Node.Tag = "" Then Exit Sub
  ' try to open the file
  Try
    Process.Start(e.Node.Tag)
  Catch ex As Exception
    MessageBox.Show("Error opening file: " & ex.Message)
  End Try
End Sub

Here it is in action:

TreeView with Icons

Resources

Related

Tags: , ,

72 Comments

  1. Thanks a lot for this tutorial. It is great but i have an issue, maybe you can explain…

    i am using windows 7 pro.

    when displaying icons no matter if it is a file or a folder it alsways displays the window icon (flying window).

    What am i done wrong? any idea? i was trying to solve it myself but the only thing i discovered is that the imagelist control has set the property “Images:(Collection)” but there is no images into the collection.

    OscarG

  2. OscarG, I have not done much development or testing in Windows 7, so unfortunately I cannot answer your question at this time.

    You might want to check out the Windows API Code Pack for Microsoft .NET Framework, which does mention this:

    “Windows® API Code Pack for Microsoft® .NET Framework provides a source code library that can be used to access some features of Windows 7 and Windows Vista from managed code. These Windows features are not available to developers today in the .NET Framework.”

    One of the features listed for the API Code Pack that you might be interested in:

    “Command Link control and System defined Shell icons”

  3. Perry,

    Thanks for this tutorial and the previous one, it is just what I needed for my file browser. I have one problem however I cannot get the open folder to work. I believe I copied the code correctly, Here is my function

    Function GetShellOpenIconAsImage(ByVal argPath As String) As Image
    Dim mShellFileInfo As New SHFILEINFO
    mShellFileInfo.szDisplayName = New String(Chr(0), 260)
    mShellFileInfo.szTypeName = New String(Chr(0), 80)
    SHGetFileInfo(argPath, 0, mShellFileInfo, System.Runtime.InteropServices.Marshal.SizeOf(mShellFileInfo), SHGFI_ICON Or SHGFI_SMALLICON Or SHGFI_OPENICON)
    ‘ attempt to create a System.Drawing.Icon from the icon handle that was returned in the SHFILEINFO structure
    Dim mIcon As System.Drawing.Icon
    Dim mImage As System.Drawing.Image
    Try
    mIcon = System.Drawing.Icon.FromHandle(mShellFileInfo.hIcon)
    mImage = mIcon.ToBitmap
    Catch ex As Exception
    ‘ for some reason the icon could not be converted so create a blank System.Drawing.Image to return instead
    mImage = New System.Drawing.Bitmap(16, 16)
    End Try
    ‘ return the final System.Drawing.Image
    Return mImage
    End Function

    How does the micon line 11 work? How does it know it is the open icon and not the closed one.

    Thanks for your help.

    Nick

  4. Nick,

    My apologies, you should have seen another line of code, but a problem with my website was preventing that line of code from being displayed in the tutorial (apparently I’m using SyntaxHighlighter Devovled.) Basically, it’s the code that adds the OPENICON to the ImageList for use by our TreeView, and without it you won’t see the icons.

    I would encourage you to check out the tutorial again as I have fixed the missing code, particularly making sure that you implement this line of code properly:

    ImageList1.Images.Add(mKey & "-open", GetShellOpenIconAsImage(argPath))
    
  5. Hello Perry, first of all: Very well done!!!

    It’s exactly what I was looking for. But I have some problems implementing it.

    1. I did part 1 of your tutorial and it worked exactly the way you’d descriped it. I referenced it just by adding your treeview_project to my project and the Usercontrol1 appeared in the Toolboxlist as expected (and worked). But after I did part 2 of your tutorial, it doesn’t work any longer. Usercontrol1 appears in the toolboxlist but trying to put it in my windowsform, nothing happens!? No exception or warning.

    2. The events from Usercontrol1 don’t work. I tried “click”,”doubleclick”,”enter”,”keydown”,etc. – only the load-event reacts. I don’t know yet how to get the actual selected path from the Control by clicking on a node/folder to do something in my project.

    Hope, you can help me with that,
    Thanks

    PS: Using vs2008, windows 7

  6. Hello, me again.

    I solved the Problem with the toolboxlist. Don’t ask me what it was, but after deleting and re-adding the usercontrol1-project in my project a few times, it (for no reason) worked again.

    I also found out, what’s the problem with the event-handlers. If I set the Dock-Property not to “Fill”, so that the usercontrol1 is dividedin a treeview-area and an empty area, the events react – but only on the empty area. I think it’s because the treeview in the usercontrol1 has it’s own eventhandlers. But I can’t use them because i need the events in my project/windowsform and not within the treeview-control itself.

    Does anyone know, how to solve this?

    I have a windows form with the usercontrol1 on the left side and a datagridview on the right side showing a list of images that are in the folder, that is selected on the left side (usercontrol). So everytime the selected folder changes I need to actualice the datagridview.

    Thanks

    PS: I encounterd another Problem in windows 7 (and as I found out in the net also in windows vista): Openning special folders like “…\applicationdata” or “\documentions and settings” throws exceptions: “file access denied”.

  7. Thomas, My apology for the confusion. I should have posted the VS project files for this tutorial so you can have a working reference point. You can now download the VS project files the end of the tutorial.

    To answer your question about the events, you need to expose the TreeView1 control publicly so you can access it through UserControl1 via a property or subroutine.

    For example, in UserControl1 you can write a public Function to expose something about TreeView1:

    Public Function GetPath() as String
      Return Me.TreeView1.SelectedNode.Tag
    End Function
    

    Now when you recompile the UserControl1 and then add it to a WinForm, you can display the selected path using:

    MessageBox.Show(UserControl1.GetPath)
    

    I hope this helps!

    Concerning Windows 7, I have just started using it myself and have noticed that in many of my Visual Studio projects where I am accessing a common path on the system (Program Files, Documents and Settings), I also receive an Access Denied message where it used to work on Windows XP. This might be from the new User Access Control (UAC), or perhaps we need to use a different method to access those paths, such as environment variables (%USERPROFILE%). I do know that Windows 7 has changed where User profiles store data (C:\Users now instead of C:\Documents and Settings), but beyond this, I haven’t done much testing yet.

  8. sir am sorry for inconvinence but i want your help i did what u have illustrated previosly but i want u to show me how can do this :
    i have (import botton ) when i click it should save the selected item on the specific node the node should have the ability to save all typ of data and should be saved in a data base too,then should show me the items on listview as soon as i click on listview item should display the selected item then i want the dso former control ,pdf ,picturebox and media player to link them to work with listview items
    thank you sir ..

  9. Good tutorial, I was about to implement my tree with recursion until I came across your example. Seems cleaner this way. Thanks for sharing.

  10. Hello, i have manged to get in all working. But what code do i need to get this to open from a button on a different form??

    Thanks

    VB 2008

    Any help would be great.

    • Callum, just add a reference to the DLL or the entire Project, then you will be able to drag & drop UserControl1 from the Toolbox onto a Windows Form. Let me know if you still can’t get it working…

  11. Perry, Thank you for posting this great code. This is by far the best explanation how to use TreeView. I got this working perfectly. However I would like to populate TreeView after user selects Output Folder instead of setting C:\ as root directory and refresh TreeView after event is completed

    I am new to VB.net, my experience is mostly with VBA and VBS

    I hope you can point me into the right direction. Thank you

  12. Peter, the code which initializes the TreeView is in the UserControl1_Load event (from Part 1 of this tutorial). I would suggest placing this code into its own Sub (you might call this Sub InitializeRoot) and then you can call InitializeRoot whenever you need to (such as when the RootPath property is changed, or when the control is loaded via UserControl1_Load). You’ll also want to do a TreeView1.Nodes.Clear() to the InitializeRoot() method.

    So, UserControl1_Load simply becomes:

    Private Sub UserControl1_Load(ByVal sender As System.Object, ByVal e   As System.EventArgs) Handles MyBase.Load
        InitializeRoot()
    End Sub
    

    Your RootPath property should also call InitalizeRoot() whenever the value changes:

    Property RootPath as String
        Get
            Return mRootPath
        End Get
        Set(value as String)
            mRootPath = value
            InitializeRoot()
        End Set
    End Property
    

    And finally, InitializeRoot(), which borrows code from the old UserControl1_Load event but also clears the nodes before populating again:

    Private Sub InitializeRoot()
        ' when our component is loaded, we initialize the TreeView by  adding  the root node
        Dim mRootNode as New TreeNode
        mRootNode.Text = RootPath
        mRootNode.Tag = RootPath
        mRootNode.Nodes.Add("*DUMMY*")
        TreeView1.Nodes.Clear()
        TreeView1.Nodes.Add(mRootNode)
    End Sub
    

    Hopefully that points you in the right direction…

  13. Thank you, I got this working. However there is small issue. mRoothPath is provided by a user after form loads, After loading form, TreeView shows small plus sign and if you click on it application crushes due to “Null Reference” (since no path was specified yet). I supposed best approach will be to have some error message displayed “please select folder” or hide this plus sign completely if its even possible.

    I had to change mRoothPath to myRootPath, because I was getting some error messages.

    I was also able to populate TreeView using some different method, but your one is much much faster 😉 Thank you again

  14. Hi Perry,

    Thank you for this wonderful code. I do have a small…actually a rather big question. I managed to add this very important component to my application (It’s a replacement shell for explorer.exe). Users need to select different folders, such as My Music etc. and not only C: and Program Files. Only rootpath seems to be malfunctioning, It doesn’t change to the specified folder the user want. I hope you could help me.

    Thank You,

    Robert

    • Robert, have a look at my reply to Peter, earlier in these comments. He had nearly the same question. Let me know if this doesn’t achieve what you’re after.

  15. Hi Perry,

    Thank you for the tip, but I have a quite different issue. I made some buttons in a form where users can select there folder. It’s not in the usercontrol itself, but in a form which use the .dll from the usercontrol. So, theoretically only: Private mRootPath As String = “path user needs”, needs to be adapted if you click the button.

    I thought you can do this in the form itself by make the mouse-click handler (you know, simple as that) and type this: Usercontrol1.Roothpath = “C:Windows” and then Usercontrol.refresh() or something like that. But when I do that, nothing happens. RoothPath can only be changed in the properties window.

    I hope you can follow my story and my problem.
    Thank You,

    Robert

    • Robert, to allow a user to change RootPath dynamically, you need to modify the UserControl1 code slightly as follows:

      UserControl1_Load simply becomes:

      Private Sub UserControl1_Load(ByVal sender As System.Object, ByVal e   As System.EventArgs) Handles MyBase.Load
          InitializeRoot()
      End Sub
      

      Your RootPath property should also call InitalizeRoot() whenever the value changes:

      Property RootPath as String
          Get
              Return mRootPath
          End Get
          Set(value as String)
              mRootPath = value
              InitializeRoot()
          End Set
      End Property
      

      And finally, InitializeRoot(), which borrows code from the old UserControl1_Load event but also clears the nodes before populating again:

      Private Sub InitializeRoot()
          ' when our component is loaded, we initialize the TreeView by  adding  the root node
          Dim mRootNode as New TreeNode
          mRootNode.Text = RootPath
          mRootNode.Tag = RootPath
          mRootNode.Nodes.Add("*DUMMY*")
          TreeView1.Nodes.Clear()
          TreeView1.Nodes.Add(mRootNode)
      End Sub
      

      And then you can set the RootPath property of the UserControl1 object at runtime (using a CommandButton or something) and the TreeView will update itself automatically.

  16. Hi Perry,

    Let me first say that this is a very useful piece of code.

    When I use this code in Windows 7 and set the root path to C:\ or Desktop, I am getting icons for the folders that look like drive icons, or the desktop icon. If I set rootpath to a folder like C:\program files, the icons for folders are correct. Any ideas on how I can make the icons display correctly for Drives, Desktop, etc, while still keeping the correct icon for folders?

    Thank you
    Ryan

  17. Hi Peery,

    Thank you very much for your code. Very Helpful.

    I have a couple of minor problems:

    1) By Changing the root to C:\ makes the icon of the drive, and of every folder thereafter a drive icon, so it is asociating c:\ as a folder, and think that due to cashing, all the other folders as well. solution?

    2) I would like to display in the treeview my network drives also. what changes do I need?

    thanks

    Ignacio

    • Hi Ignacio, 1) is due to a bug that doesn’t handle drive icons. See the rest of these comments (Rick or Stephane) for a fix. I can’t help you with 2) right this moment, so perhaps I will include it in tutorial part 3.

  18. When the root is C:\ it’s property is “Local Disk” not “folder”, so the code is matching the mKey “folder” to the image for “Local Disk”. So all the real folders show up as drive icons.

    Here is a solution that I found.
    In CacheShellIcon
    change this line:
    If IO.Directory.Exists(argPath) = True Then
    mKey = “folder”

    to this:
    If IO.Directory.Exists(argPath) = True Then
    If argPath = IO.Directory.GetDirectoryRoot(argPath) Then
    mKey = “Local Disk”
    Else
    mKey = “folder”
    End If

    This is all new to me so there may be a better way to do this.

  19. Thanks for this exceptional tutorial, it has proven most useful so far. One thing I’m having trouble with, and I’m not the most experienced when it comes to VB, is getting this user control let the program form manage the onclick. In other words, the program I’m inserting this control into, I want IT to manage what happens when I click on a file/folder.

    In your example, you can double-click to open a file… I need my program to control what happens onclick or doubleclick. Is there a way to do that? Thanks again!

  20. Very Very Nice Code with good explanation. It’s useful in any project which deals with file browser etc.
    Thank you very much; I was searching this since a while really.

  21. Hey Perry,

    Big thanks for the tutorial I’m building a self serve portal for users to an easier view ACL’s on folder and it’s work beautifully using DirectorySecurity and AuthorizationRuleCollection.

    However in my TreeView all my icons disaply correctly for files! but for folders then all show up as the “Drive” icon…

    I’m on windows 7 but issue seems different dans Oscars?

    any ideas?

    thanks

    • Stephane, thank you for pointing out this bug. Yes, it’s a BUG! I hope to fix this in tutorial part 3. I know…it has been a long time since part 2, as I have been super busy getting my freelance business off the ground so I don’t have to work two jobs during these tough times. I do regret not having part 3 available sooner because I’m still getting hundreds of hits a month for this tutorial and I know people need the info, as I desperately did at one time.

      I may be able to help you though, I did some code modifications that fixed it for me in Windows 7 (32-bit). The reason this bug happens is because of the logic in the CacheShellIcon function. Basically it does not handle drive icons at all, but a folder icon is cached for a drive, because a drive is also a folder and so it satisfies the conditional statement.

      To fix this, I modified the CacheShellIcon function to check if the path is a drive BEFORE assuming it is a folder (which would always equal true):

      
          Function CacheShellIcon(ByVal argPath As String) As String
              Dim mKey As String = Nothing
              ' determine the icon key for the file/folder specified in argPath
              If IO.Directory.Exists(argPath) = True Then
                  If argPath = IO.Directory.GetDirectoryRoot(argPath) Then
                      mKey = "drive"
                  Else
                      mKey = "folder"
                  End If
              ElseIf IO.File.Exists(argPath) = True Then
                  mKey = IO.Path.GetExtension(argPath)
              End If
              ' check if an icon for this key has already been added to the collection
              If ImageList1.Images.ContainsKey(mKey) = False Then
                  ImageList1.Images.Add(mKey, GetShellIconAsImage(argPath))
                  If mKey = "folder" Then ImageList1.Images.Add(mKey & "-open", GetShellOpenIconAsImage(argPath))
              End If
              Return mKey
          End Function
      

      Special thanks to Rick who posted this fix much sooner!

  22. Hi Perry,
    Than kyou for your code. It’s working good and with icons.
    But I’m stuck using it on a windows form application . The Icons wont show.

    I’ve added Form1.vb to your project. Copied usercontrol content into Form1.vb, and in designer I added the textbox, button, treeview, imagelist, and colordialog.
    The Form1 filebrowser works, but the ICONS does not show. I just have the standard + signs in the treeview.

    I’m probably missing something here, Could you point me in the right direction, on using your code it in another windows form?
    Thank you very much.
    Per

  23. Per, thanks for writing. You said it’s “working good and with icons”, I assume that is when you Build the application in Visual Studio and it loads into the “Test Container”?

    First check: In the Properties for the TreeView control, make sure the ImageList property is set to ImageList1 (or whatever your ImageList is named).

    When you create the TreeView UserControl per the tutorial, it encapsulates the ColorDialog and the ImageList for you, so all you need is to add the TreeView UserControl to a Windows form. Perhaps you were trying to copy the code from the UserControl’s code file into a Form’s code file, or building the TreeView directly in a Windows form, which is sort of reverse engineering the usefulness of the UserControl! I recommend that you try the UserControl because it’s re-usable.

    Here’s how it works…

    I added a Windows form to the TreeView project and opened it in the designer. From the Toolbox I was able to drag the TreeView UserControl straight into the Windows form and it appeared immediately (with icons) in the designer.

    If you have a separate project and you wish to use the TreeView UserControl, you can add it to that project. With your new Windows form project open, in the Toolbox, right click anywhere and select “Choose Items…”, click “Browse…”, locate your TreeView UserControl’s DLL file (usually in your TreeView project’s bin/debug folder), then click “OK”. This will add “UserControl1” to the Toolbox and you may drag it onto your Windows form.

    Though, that won’t allow you to modify the internal code of the TreeView UserControl. If you want to modify it while developing a separate project without hassle, you can always add the TreeView UserControl project to your current project (via File -> Add -> Existing Project…). This will give you direct access to using the TreeView UserControl in your project from the Toolbox, as well as the ability to change your TreeView UserControl code and rebuild it. Therefore when you change your TreeView UserControl, Visual Studio will rebuild the control for you and the new one is automatically used in your current project. I have found this to be the most useful way to manage solutions with more than one custom control.

    Please write back if that doesn’t help.

  24. Carlos Gaona says:

    First of all, good work! and a million thanks, this has helped me a lot!.
    I just wanted to add, that i needed to find a solution to fix the horrible exception thrown when accesing a protected/system directory under win7 or vista. So what I did was to modify the TreeView1_BeforeExpand sub and enclosed both For Each in a Try Catch block like this:

    Try
    For Each mDirectory As IO.DirectoryInfo In mNodeDirectory.GetDirectories

    Next
    For Each mFile As IO.FileInfo In mNodeDirectory.GetFiles

    Next
    Catch ex As UnauthorizedAccessException
    ‘ Change node name to let know the user that he/she desn’t have enough permissions to list that directory
    e.Node.Text = e.Node.Text & ” *Insufficient privilieges.*”
    End Try

    Now the control doesn’t throw any exception to the user, and he can know why he can’t list that particular directory.

    Thanks again and best regards to all!

  25. Carlos Gaona says:

    P.S. I forgot to tell you that I’m looking forward to part 3…

  26. Federico Allegretti says:

    really Nice.

    It works for me (exept i cannot get icons on the treeview but this is only and estetical problem).

    Now i need to access to the “whole” filesytem …. no root folder to “c:\” i need also “\\servername” or be able to access to tthose drives …. something like having “my computer” as root folder instead of “c:\” ….

    in every case … GREAT WORK

  27. Yudistira says:

    Hi Perry,
    I’m Yudistira
    it’s a great tutorial…, I build my application from this tutorial, but if I want to make this TreeView as TreeListView how can I do that because I’m new in vb.net thank’s a lot for share…

  28. the code works perfectly but can you please tell me how i find out with node the user has selected?

  29. to get all drive, first change RootPath into

    Property RootPath(ByVal mRootPath As String) As String

    and in form load put this instead
    Dim mRootNode As TreeNode

    For Each drive As String In IO.Directory.GetLogicalDrives()
    mRootNode = New TreeNode
    mRootNode.Text = RootPath(drive)
    mRootNode.Tag = RootPath(drive)
    mRootNode.ImageKey = CacheShellIcon(RootPath(drive))
    mRootNode.SelectedImageKey = mRootNode.ImageKey & “-open”
    mRootNode.Nodes.Add(“*DUMMY*”)
    MyTreeView.Nodes.Add(mRootNode)
    Next drive

    for win-7 folder image problem, you need to do this
    Function CacheShellIcon(ByVal argPath As String) As String
    Dim mKey As String = Nothing
    ‘ determine the icon key for the file/folder specified in argPath
    If IO.Directory.Exists(argPath) = True Then
    If argPath.Trim.Length = 3 Then
    mKey = “drive”
    Else
    mKey = “folder”
    End If
    ElseIf IO.File.Exists(argPath) = True Then
    mKey = IO.Path.GetExtension(argPath)
    End If
    ‘ check if an icon for this key has already been added to the collection
    If ImageList1.Images.ContainsKey(mKey) = False Then
    ImageList1.Images.Add(mKey, GetShellIconAsImage(argPath))
    End If
    Return mKey
    End Function

    • Thanks for sharing that Andi.

    • under VS2010 + Debug|\Releasex86 + W7x64 + russia_culture
      ———
      Problem:
      error may occur when running the program: debug everything is OK, but when I run “myProgram.exe” will be a strange behavior in the construction of the root node of the tree (no all the drives visible in top-nodes roots tree or can not work with the root tree, no visible (or destroy) root disk in Tree when starting myPrograms from the same disk … and other).
      However, no any error messages there.
      Solution:
      in line … ‘Declare Auto Function SHGetFileInfo’ … – you need to replace the ‘Auto Function’ on ‘Funtstion Ansi’.
      Result: All work – ok.

      Reason of problem:
      error when processing separator “Disk \ \ Folder” in Windows

    • Thanks LEXX for your feedback! When I revisit these tutorials to write Part 3, I will have your issues in mind.

  30. Thanks so much. You’ve saved me hours on my way to a custom dialog!

  31. Hi Perry, Im tryinng to use this control in my web application and im having hard time to do that. Could you please guide me for that.

    Thank You.

  32. How can you introduce file filtering in file selection?

    Thanks

  33. in W7 default does not work SHGFI_OPENICON and call SHGFI_OPENICON will be processed as = SHGFI_ICON ()
    justification
    – behavior of folder icons when running in Win-Explorer
    use SHGFI_OPENICON – there is not for W7 and is not works in W7

  34. my apologies. I have no time now verify large text in English, so take advantage of google translation from Russian
    (Remove if it is not)

    Here is my vision problems:

    of API coding
    Source: msdn.microsoft.com/ru-ru/library/4zey12w5.aspx
    ANSI platforms: Windows 95, Windows 98 or Windows Millennium Edition
    Unicode platforms: Windows NT, Windows 2000 or Windows XP

    Auto: packaging line according to the rules. NET Framework based on the external reference name or aliasname, if they are

    at Auto: The calling initially encoded platform that is running, and if it fails in the second choice

    BUT!
    IMPORTANT!
    with misspellings block trap errors in the code and call processing code API-functions will lead to that mode Declare Auto Function happens switch
    those with Auto – always held the very first order!
    systems Windows NT – it will always = Unicode, and Windows 98 = always ANSI
    also finished the program code itself can be VBasic Ansi (unless otherwise specified in the assembly in Relise.exe) and does not understand is Unicode code
    ‘- This is the main source of error

    in this mode when running from Debag most Visbasits
    – Code initially = Unicode, and it is performed during Debag – without errors, and transferred to the Relase – there will be errors.

    and additionally see http://support.microsoft.com/kb/319340
    example where there MarshalAs used in the block differently than you
    and this code works for me = OK = Auto or Ansi (no matter your choice)
    Then when your code only in Ansi
    code http://support.microsoft.com/kb/319340 – see separate folder icons
    your code – art created puts the first choice for all icons

    noteI moved the code from a control on a form with a TreeView and Imagebox, and not used as a separate user control ready

    good luck

  35. Your article helped me get my head around the treeview and get things working for me. Now if we can just clone you (make you a Sub ) and call you! Thanks again!!!!!

  36. Hi Perry,

    Thank you for this great website. I am looking forward to Part 3 of this article.

    May I request that you also develop and post a similar Treeview-based custom control that displays and manipulates data from a SQL database table, preferably using stored procedures.
    Thanks again!

  37. Hey there, I’m struggled to hide the filetype and the system files, also get the icons from app in shortcut.

    Awaiting to hear from you. I’m trying to get the start menu back cos i’m using Windows 8.

    Thanks
    Jared

  38. Lucas S.Wundervald says:

    how to do the same project in c #?

  39. Mark McCumber says:

    Recently I came across your tutorial for making this efficient User defined TreeView control.

    I’m using Visual Studio 2008 on a Windows 8.1 system. The issues I’m having are: 1). Can’t get the Shell32.dll to display a plain drive icon(no blue Windows symbol above it). 2). When I expand the drive. the icons for the Folders are showing an “Open” folder icon and the Folder hasn’t been expanded yet. 3). Why can’t I list my files in my Documents and Settings or My Documents folders? The application I’m making and want to use the TreeView Control for must allow the user access to aforementioned folders/directories.

    Is there any way to fix these issues?

    I have made many of the modifications referred to in this blog. I will gladly send you the source code if you think it will help.

    Thanks,
    Mark

    • Hi Mark, while I currently don’t have access to a Windows 8 machine, I can say that it seems to work fine in a Windows 7 64-bit environment. Have you tried downloading the project at the end of this tutorial to see if the issue is there?

      As for “Special” folders such as My Documents, Desktop, etc, I’ve been saving them for Part 3 of the tutorial series, which I haven’t started writing yet.

    • Mark, I changed the RootPath to C:\ and now I’m seeing the issue. It may be a few days before I can get back to you with a solution.

  40. Mark McCumber says:

    I downloaded the TreeViewFileBrowserPart2 source code, and I modified it to show all drives and the volume labels of each in:

    Private Sub InitializeRoot()
    Dim mRootNode As TreeNode
    Dim drives As Collections.ObjectModel. _
    ReadOnlyCollection(Of IO.DriveInfo) = My.Computer.FileSystem.Drives
    TreeView1.Nodes.Clear()
    For I As Integer = 0 To drives.Count - 1
    If Not drives(I).IsReady Then
    Continue For
    End If
    mRootNode = New TreeNode
    mRootNode.Text = drives(I).VolumeLabel.ToString() & _
    " (" & RootPath(drives(I).Name) & ")"
    mRootNode.Tag = RootPath(drives(I).Name)
    mRootNode.ImageKey = CacheShellIcon(RootPath(drives(I).Name))
    mRootNode.SelectedImageKey = mRootNode.ImageKey & "-open"
    mRootNode.Nodes.Add("*DUMMY*")
    TreeView1.Nodes.Add(mRootNode)
    'Modifications to use ImageList
    mRootNode.ImageKey = CacheShellIcon(drives(I).Name)
    mRootNode.SelectedImageKey = mRootNode.ImageKey
    Next
    End Sub

    The RootPath property is modified as:

    Property RootPath(ByVal mRootPath As String) As String
    Get
    Return mRootPath
    End Get
    Set(ByVal value As String)
    mRootPath = value
    InitializeRoot()
    End Set
    End Property

    I have noticed a few minor details that I thought needed addressing: 1). The text for system and portable hard drives are in this format: Volumnlabel (Drive:\) Example: Windows (C:\) or Lacie (G:\). However a removable disk like a flash drive shows a hard drive icon, not a Removable disk icon.

    I want to use this control in a personal application, so that is why I need access to the special folders.

  41. Mark McCumber says:

    Thanks. I’ll be interested in what you come up with.

    • Mark McCumber says:

      Perry,
      For grins and giggles I took the EXACT code from your TreeViewFileBrowserPart2 tutorial and placed it in a Windows application.

      Can you enlighten me as to when the code is placed in a User defined control project the TreeView control displays the folders and files properly, but when placed in a WIndows application project the TreeView control on the form displays nothing. No nodes at all.

      From my KISS POV if it works in one place it should work in the other.

      This is one example of the inconsistencies I seem to encounter when using VB.NET.

      Thanks,
      Mark

    • Mark McCumber says:

      For those who are interested. I have corrected the problem in the “Grins and Giggles” post. Here is the code for the Form_Load Event:

      Private Sub frmTreeView_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs) _
      Handles Me.Load
      'Purpose: Make every visible drive a RootNode

      Dim mRootNode As TreeNode
      Dim drives As Collections.ObjectModel. _
      ReadOnlyCollection(Of IO.DriveInfo) = My.Computer.FileSystem.Drives
      tvwFolders.Nodes.Clear()
      For I As Integer = 0 To drives.Count - 1
      If Not drives(I).IsReady Then
      Continue For
      End If
      'Make new RootNode
      mRootNode = New TreeNode(drives(I).Name.ToString)
      'Show both the Drive's Volume and Name on the RootNode
      mRootNode.Text = drives(I).VolumeLabel.ToString & _
      " (" & drives(I).Name & ")"
      mRootNode.Tag = drives(I).Name.ToString
      'Set DUMMY node so it is expandable
      mRootNode.Nodes.Add("*DUMMY*")
      'Add RootNode to TreeView
      tvwFolders.Nodes.Add(mRootNode)
      'Modifications to use iIconList
      mRootNode.ImageKey = CacheShellIcon(drives(I).Name)
      mRootNode.SelectedImageKey = mRootNode.ImageKey
      Next
      End Sub

      I’m still in the process on how to allow access to special folders like “Documents and Setting” and “Documents”, so far nada. The NTFS Security class isn’t much help. The examples I have found are not allowing me the set the access permissions for the previously mentioned directories, but I’m going to keep slugging away.

    • Thanks for sharing. I still haven’t had time to dive into this yet, but here’s a few notes:

      Documents and Settings was a Windows XP thing. It’s now the Users folder in Windows 7/8.

      See here for special folders: System.Environment.SpecialFolder Enumeration

      I tested to make sure the UserControl can be added to a Windows Form. Here’s how I did it. Create a new WinForms project, then Add Existing Project > TreeViewFileBrowserPart2.vbproj. Now run your WinForms app once, then stop it, so that it builds both projects and puts the UserControl1 into your Toolbox. Now drag UserControl1 from the Toolbox onto the form. It should appear on the form with a path of C:\program files. If you click the UserControl1 you can change the RootPath property to something else.

      Hope that helps…

  42. Mark McCumber says:

    Perry,
    The Grins and Giggles application just reused your UserControl1 code inside a Windows application.I just placed a TreeView control on a form. The only thing I changed in your code was the UserControl1_Load event code was replace by the Form_Load event code I posted.

    I read the link you provided and I reran my sorry excuse of an application and something struck me.

    When the app makes the Tree Nodes the Documents and Setting node appears under the C:\ drive.as one of its children, but when I look in Windows Explorer no such directory exists. I wonder why.

    Since you pointed out that Documents and Setting is now referred to in Win 7 & 8 as Users. Using my app I had no problem drilling down the tree. Another I wonder …

    Looks like some tweaking needs to be done for appearance’s sake.

    • Mark McCumber says:

      I have tweaked the TreeView1 control until it looks more like the TreeView control in Windows Explorer. Before I share my explorations. I have to give Perry credit for his wonderfully efficient TreeView code. It is much easier to experiment with your ideas when you have a solid foundation to work upon.

      Okay here is what I did. If someone has a more better idea. I would like to know.

      In the TreeView1_Before Expand event make the following modifications:
      Under the
      For Each mDirectory As IO.DirectoryInfo In mNodeDirectory.GetDirectories
      ‘ declare a TreeNode for this directory
      Dim mDirectoryNode As New TreeNode

      Place this code:
      ‘Check to see it directory is a hidden or system directory.
      ‘Only place non-hidden or non-system directories in the TreeView
      If ((File.GetAttributes(mDirectory.FullName) And _
      IO.FileAttributes.Hidden) = IO.FileAttributes.Hidden) Or _
      ((File.GetAttributes(mDirectory.FullName) And _
      IO.FileAttributes.System) = IO.FileAttributes.System) Then
      Continue For
      End If
      This code skips any directory that has hidden or system attributes.

      There is a similar modification contained in:
      For Each mFile As IO.FileInfo In mNodeDirectory.GetFiles
      Place this code underneath the code line above:
      ‘Don’t want to show files with no extension, .sys, .ini, .xml, or .sqlite
      ‘extensions underneath the Windows directory
      If mFile.Extension = “.sys” Or mFile.Extension = “” Or _
      mFile.Extension = “.ini” Or mFile.Extension = “.xml” Or _
      mFile.Extension = “.sqlite” Then
      Continue For
      End If
      This code does not list these file types that reside in the C:\ directory.

      Thanks,
      Mark McCumber

  43. Mark McCumber says:

    Perry,
    I noticed something today when I open up Windows Explorer on my WIndows 8.1 machine.

    The Folder icons are being shown as “open” folder icons. I think this is the Shell32.dll default in Windows 8.1. So I think that is how it is going to be when using the TreeView Control in a Windows 7/8 environment.

    One thing I still haven’t figured out yet is what is the proper icon for USB flash drives? Currently it shows a hard drive icon with the blue windows.

  44. Perry,

    Thanks for the great info. Everything worked perfectly for me in Visual studio 2012. Very much appreciated. I do have a couple of questions though.

    First, is it possible to filter a treenode so that it only displays certain file types within the folders? I am trying to only show .mp3 files in a music directory, and I am seeing mp3’s, wma’s, etc, and the album artwork.

    Second, I would like the double clicking of an mp3 file within the treeview to play in my application using on screen audio control buttons (play, pause, FF, RW, etc) without opening windows media player (or another default media player). Essentially I am creating my own audio player on a form that is built into a much larger app for home automation and home media. If this is not something that you can easily do, can you please point me in the right direction for this?

    Thanks.

    Brennan

  45. Perry,

    I figured out how to only display certain files within the tree view by playing around a little. If anyone else is interested, the modified code is below. I basically added an if then statement in the part where it adds nodes to the treeview.

    ‘ add each file from the file system that is a child of the argNode that was passed in
    For Each mFile As IO.FileInfo In mNodeDirectory.GetFiles
    ‘ declare a TreeNode for this file
    If mFile.Extension = “.mp3” Then
    Dim mFileNode As New TreeNode
    ‘ store the full path to this file in the file TreeNode’s Tag property
    mFileNode.Tag = mFile.FullName
    ‘ set the file TreeNodes’s display text
    mFileNode.Text = mFile.Name
    mFileNode.ImageKey = CacheShellIcon(mFile.FullName)
    mFileNode.SelectedImageKey = mFileNode.ImageKey & “-open”
    ‘ add this file TreeNode to the TreeNode that is being populated
    e.Node.Nodes.Add(mFileNode)
    End If

    Now to figure out how to do the music playing part in the click event of an mp3 in the treeview without using a default media player…

    Thanks,

    Brennan

    • Thanks for sharing Brennan! As for the music player, I know you don’t want to use the default music player (Windows Media Player), but did you know that you can utilize Windows Media Player as a programmable control directly in your own VB.NET Form? Check out this CodeProject article or this DreamInCode article to learn more.

      The reason you might want to reconsider using WMP is due to the nature and difficulty of programming a media player from scratch – you’d have to write your own codecs (or use code libraries) to decode specific audio formats and streams, but with WMP all that work is done for you. You can use simple functions to have WMP load a file and play that file.

      Perhaps this code will inspire you to experiment a bit with WMP:

      Dim WithEvents Player As New WMPLib.WindowsMediaPlayer

  46. thanx a lot perry, im trying to come up with a disk space analyser app that uses treeview
    here is my problem : i would like to display both file name or folder name and SIZE so that i can see which folders are using more space
    please help
    thanx in advance

    • You would have to do a recursive search of that directory and add up all the file sizes, which can be slow, and if you did do that you might as well build the whole TreeView using recursive methods instead of the on-demand method which I’ve shown in the tutorial. It sounds like you’re trying to create something like TreeSize Personal which is a pretty good program 🙂

  47. perry thanx a lot for your code
    but i need some help. i wanted my treeview to calculate the size for each folder, but now its refusing to access the folder users\app data

    please help

  48. Richard Martin Uchuya Ramos says:

    No entiendo el idioma Ingles pero, esta muy bien tu aplicacion y tu tutorial pero yo desdeo saber los codigos para explorar mi sitema igual como lo hace tu pero ademas la propiedad de cambiar de color al treeview y al foretext del treeview deben estar activadas, en tu aplicacion no estan activadas esas dos propiedades

  49. Joginder Nahil says:

    I am interested in the file browser component that includes majority of the good ideas above and bug fixes. Is there such an animal?

  50. Hello Perry, I’m only responding to tell that your treeview code is far best I’ve ever seen. It works perfect on Windows 7 and you can use It in many ways. I use It to navigate in local folders with Webbrowser control to enable all actions (delete/cut/copy/paste/drag/drop). It’s funny that in combination with webrowser you can list image files directly in It by clicking on image – something like a print preview look :)….Anyway, thanks a lot for a perfect article, I THANK YOU A LOT !!!

Leave a Reply

Your email address will not be published. Required fields are marked *