Show network folder icon in listview VB.NET

妖精的绣舞 提交于 2019-12-04 05:22:12


how do I show network folder icon in a listview? the one with the green tube under the folder, I have the code that works great with files and folders but when visiting other computer through network, I can't see the network folders that looks like this one.

what should I add?

here's my code: this is how I display icon in a ListView

Dim fPath As String = Form2.TextBox1.Text
Dim di = New DirectoryInfo(fPath)

  ' store imagelist index for known/found file types
  Dim exts As New Dictionary(Of String, Int32)

  If di.Exists = True Then
      Dim img As Image
      Dim lvi As ListViewItem
      For Each d In di.EnumerateDirectories("*.*", SearchOption.TopDirectoryOnly)
          lvi = New ListViewItem(d.Name)


          img = NativeMethods.GetShellIcon(d.FullName)
          lvi.ImageIndex = ImageList1.Images.Count - 1

this is how I get icons from shell32.

Partial Public Class NativeMethods
Private Const MAX_PATH As Integer = 256
Private Const NAMESIZE As Integer = 80
Private Const SHGFI_ICON As Int32 = &H100
    Private Structure SHFILEINFO
        Public hIcon As IntPtr
        Public iIcon As Integer
        Public dwAttributes As Integer
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=MAX_PATH)>
        Public szDisplayName As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=NAMESIZE)>
        Public szTypeName As String
    End Structure
    Private Shared Function SHGetFileInfo(ByVal pszPath As String,
                                      ByVal dwFileAttributes As Integer,
                                      ByRef psfi As SHFILEINFO,
                                      ByVal cbFileInfo As Integer,
                                      ByVal uFlags As Integer) As IntPtr
    End Function
    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As Boolean
    End Function
    Public Shared Function GetShellIcon(ByVal path As String) As Bitmap
        Dim shfi As SHFILEINFO = New SHFILEINFO()
        Dim ret As IntPtr = SHGetFileInfo(path, 0, shfi, Marshal.SizeOf(shfi), SHGFI_ICON)
        If ret <> IntPtr.Zero Then
            Dim bmp As Bitmap = System.Drawing.Icon.FromHandle(shfi.hIcon).ToBitmap
            Return bmp
            Return Nothing
        End If
    End Function
End Class


You can get that icon from Shell32 as a small or large image. As Cody Gray points out in a comment, there are more icons (200+) in "imageres.dll". To get these by index, add this method to your NativeMethods class:

<DllImport("shell32.dll", CharSet:=CharSet.Auto)>
Private Shared Function ExtractIconEx(szFileName As String,
                            nIconIndex As Integer,
                            ByRef phiconLarge As IntPtr,
                            ByRef phiconSmall As IntPtr,
                            nIcons As UInteger) As UInteger
End Function

Private Shared ImgResFile As String = "imageres.dll"
Private Shared ShellFile As String = "shell32.dll"

Friend Shared Function GetShellIconByIndex(ndx As Int32,
                         largeIcon As Boolean = False,
                         Optional FromShell As Boolean = True) As Bitmap
    Dim largeIco As IntPtr
    Dim smallIco As IntPtr
    Dim thisIco As IntPtr
    Dim ico As Icon
    Dim bmp As Bitmap = Nothing

    Dim targtFile = If(FromShell, ShellFile, ImgResFile)
    ExtractIconEx(targtFile, ndx, largeIco, smallIco, 1)

        If largeIcon Then
            ico = Icon.FromHandle(largeIco)
            thisIco = largeIco
            ico = Icon.FromHandle(smallIco)
            thisIco = smallIco
        End If
        bmp = ico.ToBitmap()
    Catch ex As Exception           ' swallow exception to return nothing
        ' really stupid index values can throw ArgumentException
        ' when the result is IntPtr.Zero
        ' Rather than test it, catch it an any other(s)
    End Try

    Return bmp
End Function

The first argument is the index of the icon to get, the second indicates whether you want the large or small version, the last is an optional flag to fetch from imageres.dll versus shell32.dll. Note that the method can result Nothing if something goes wrong.

Then modify your folders loop to get the pipe-folder image (#275) from shell32.dll when you detect a network drive:

For Each d In di.EnumerateDirectories("*.*", SearchOption.TopDirectoryOnly)
    If IsNetworkFolder(d) Then
        ' get #275 as small image from Shell 
        img = NativeMethods.GetShellIconByIndex(275, False)
        If img Is Nothing Then
            ' ToDo: perhaps load a default image from Resources?
        End If
        img = NativeMethods.GetShellIcon(d.FullName)
        If img Is Nothing Then
            img = IconFromFile(d.FullName)
        End If
    End If
    '... add code 

Private Function IsNetworkFolder(di As DirectoryInfo) As Boolean
    Dim drv As New DriveInfo(di.Root.Name)
    Return (drv.DriveType = DriveType.Network)
End Function

This uses a helper function to determine whether the folder is networked or not. If it is, it fetches that specific folder icon, which is #275, from the DLL. Result:

That same folder image is also in imageres.dll as #137 (and #68 and #69 are similar with world overlays). To get from that instead:

'  137 is the index, false for large icon, false to use imageres instead: 
img = NativeMethods.GetShellIconByIndex(137, False, False)

If you want to avoid Magic Numbers in your code, use constants or an enum of the icons used. You could define them all in the NativeMethods class, but that is 500 items and you may well not recall what they mean 6 months later:

Private Enum ShellIcons
    NetworkFolder1 = 275
    NetworkFolder2 = 103
    SharedFolder = 158
    AddNetworkFolder = 278
End Enum
img = NativeMethods.GetShellIconByIndex(ShellIcons.NetworkFolder1, False)

This will display the icons stored in shell32.dll and their index to a Listview set to LargeIcon View so you can browse them:

Dim ndx As Int32 = 0
Dim img As Image = Nothing
Dim lvi As ListViewItem

    ' change second Bool to False to get the ones in imageres.dll
    img = NativeMethods.GetShellIconByIndex(ndx, True, True)

    If img IsNot Nothing Then
        lvi = New ListViewItem(ndx.ToString)

        lvi.ImageIndex = ImageList1.Images.Count - 1
        ndx += 1
        Exit Do
    End If
Loop Until img Is Nothing