[File Icons]

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: