Notes on the system image list

If you need the icons for files in a Windows program, the easiest way to do so is with the system image list. This is an image list (a resource containing icons, mapped by index) that caches those system icons. The advantage of being an image list is you can easily associate it with a control (like a ListView) and pick out the images by their index, or draw out of it.

Note that in my examples, I only acquire a small image list. You should be able to replace references to SHGFI_SMALLICON to SHGFI_ICON to get the large icons, and have an additional list as a result.

First, to quickly acquire the image list, I do call SHGetFileInfo like so:

SHFILEINFO sfi;
TCHAR windir[MAX_PATH];
ZeroMemory(&sfi, sizeof(sfi));
/* this is so we get a dir that we know exists */
GetWindowsDirectory(windir, MAX_PATH);
HIMAGELIST sil = (HIMAGELIST)SHGetFileInfo(
	windir,
	0,
	&sfi,
	sizeof(SHFILEINFO),
	SHGFI_SYSICONINDEX | SHGFI_SMALLICON);

Note that the system image list is a singleton; you can rerun this to get the same image list, or cache it. Because it’s a singleton, you shouldn’t change it (except through asking for more icons). If you change it or destroy it behind the system’s back, very bad things happen. (Read below if you’re curious what those bad things are.)

You can bind image list you received can be to a control using the appropriate means. For example, for a ListView, you want ListView_SetImageList.

Then, you can use the same function you used to acquire the image list to actually get the index for a path:

SHFILEINFO sfi;
ZeroMemory(&sfi, sizeof(sfi));
SHGetFileInfo(path, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
/* The icon index in the SIL is sfi.iIcon */

If you attached the SIL to a control, just use that index when you’re i.e. adding an item. It will automatically reuse existing indices if they’re already in the SIL. I suspect caching indices based on extensions may be an easy way to get more performance.

There is one gotcha I’ve never seen mentioned before. Some controls like a ListView will destroy the image list (or at least empty its contents) as part of their normal destructor. To deal with it, change the image list to a null one before it gets called. For example, in a simple raw Win32 dialog with a ListView, I did this in the dialog’s destructor window message:

case WM_DESTROY:
	{
		HWND lv = GetDlgItem(hwnd, IDC_DIALOG_LISTVIEW);
		ListView_SetImageList(lv, NULL, LVSIL_SMALL);
	}
	return TRUE;

Edit: Or you can just apply the LVS_SHAREIMAGELISTS window style, which won’t destroy the image list along with the control.

Trashing the system image list will break anything using it. Windows depends on nothing changing the system list other than itself; for example, it won’t replenish icons that something has deleted. When icons are missing, things like stock file dialogs may not have any icons anymore. (This was a lot worse back in the Windows 9x days, because it wasn’t a process-local singleton, but a system-wide one. Enjoy rebooting to fix it!)

For more cursed tricks, check out James Brown’s article on it.

Leave a Reply

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