Note
Friday, August 17, 2001
Beta 2 made what I did here impossible, as an ImageList can't be created on a handle anymore. Check out the WinShell.NET on the downloads page for a new approach. However, the crap on using SHGetFileInfo is still as valid as it were.
Introduction
Let's talk icons. You might expect some lengthy hardcore stuff here, but it's quite straightforward. Although the framework classes (as of Beta 1, at least) do not support extracting an icon associated with a file type, there is a major improvement to the ImageList class (yes, it's a "control", but bear with me): you can create it based on the system image list.
Icons
In VB6, you could attach the system image list to controls like TreeView or ListView using an API (TreeView_SetImageList, for example). But VB would remove that once it found that it wasn't an ImageList from the common controls library. You could hack arround that by subclassing the TreeView (hacking into it's WndProc). Need I say more?
Another way was to extract the icons, using APIs like ExtractAssociatedIcon. You had to clean it up afterwards, not to mention that you ended up with duplicated icons in the first place.
Now, in VB.NET you still need one API: SHGetFileInfo. This will return a handle to the system image list if you feed it the right flags. You can then create a new instance of WinForms.ImageList based on that handle (note that we're calling a wrapper here):
m_ImageList = New ImageList(GetSystemImageList)
This image list allows you to access several hundred icons, among them all the icons that are associated with file types, by index. No clean-up code other than calling "Dispose" on the image list is necessary. You'll see how to find the right index for every file, soon.
To make this work, add the following declarations. Also, you need a couple of functions to use these monsters in a wrappy, Basic manner:
Imports System.Runtime.InteropServices Imports Microsoft.VisualBasic ' ... Private Declare Function SHGetFileInfo Lib "shell32.dll" Alias "SHGetFileInfoA"( _ ByVal pszPath As Integer, _ ByVal dwAttribs As Integer, _ ByRef lpfi As TSHFileInfo, _ ByVal cb As Integer, _ ByVal flags As SHGFI) _ As Integer Private Enum SHGFI SmallIcon = &H1 LargeIcon = &H0 OpenIcon = &H2 SysIconIndex = &H4000 UseFileAttributes = &H10 End Enum <StructLayout(LayoutKind.Sequential)> _ Private Structure TSHFileInfo Public hIcon As Integer Public iIcon As Integer Public dwAttribs As Integer <MarshalAs(UnmanagedType.LpStr, SizeConst:=260)> _ Public szDisplayName As String <MarshalAs(UnmanagedType.LpStr, SizeConst:=80)> _ Public szTypeName As String End Structure Public Function GetSystemImageList( _ Optional ByVal fLarge As Boolean = False) _ As Integer ' alloc sys string Dim pszPath As Integer = Marshal.StringToHGlobalAnsi("C:\") ' set flags Dim flags As SHGFI = SHGFI.SysIconIndex If fLarge Then flags = flags BitOr SHGFI.LargeIcon Else flags = flags BitOr SHGFI.SmallIcon End If ' structure, call, return Dim fi As TSHFileInfo Return SHGetFileInfo(pszPath, 0, fi, SizeOf(fi), flags) End Function Public Function GetSystemIconIndex( _ ByVal sFile As String, _ Optional ByVal fOpen As Boolean = False, _ Optional ByVal fLarge As Boolean = False, _ Optional ByVal fUseExt As Boolean = False) _ As Integer ' alloc sys string Dim pszPath As Integer = Marshal.StringToHGlobalAnsi(sFile) ' set flags Dim flags As SHGFI = SHGFI.SysIconIndex If fOpen Then flags = flags BitOr SHGFI.OpenIcon End If If fLarge Then flags = flags BitOr SHGFI.LargeIcon Else flags = flags BitOr SHGFI.SmallIcon End If If fUseExt Then flags = flags BitOr SHGFI.UseFileAttributes End If ' structure, call, return Dim fi As TSHFileInfo SHGetFileInfo(pszPath, 0, fi, SizeOf(fi), flags) Return fi.iIcon End Function
Note that structures are not allowed to use either fixed-length string or arrays with pre-declared bounds. That's a bit of a problem with a structure like this (which, in it's C declaration, uses char arrays [which might work like pointers, but the arrays are allocated in the structure nevertheless]). Therefore, the structure takes so-called marshalling attributes, which describe the preferred layout in memory.
Using the image list
Next, you hook up the image list to, say, a ListView control. When you want to add a file, you call GetSystemIconIndex, and use the return value for the ImageIndex property:
Dim fil As System.IO.File(sFile) Dim li As New ListItem(fil.Name, fil.FullName) li.ImageIndex = GetSystemIconIndex(sFile)
You can pass three optional flags to GetSystemIconIndex:
- fOpen: The icon in open state. It's used for folders only. Also note that file system folders (though not special folders) always use the icon with the indices three and four (for normal and open state, respectively).
- fLarge: Works for the image list with the large (32x32) icons. Note that you need to call GetSystemImageList with the same flag and hook the image list up to the LargeImageList property of ListView.
- fUseExt: This is really intersting. You can pass any arbitrary file name or even extension, and the function will assume it exists and give you the icon. This way, you can get the icon index for ".txt" files, or add icons to an FTP browser.