Getting shell item information

by Kent Reisdorph

Last month in the article, “Enumerating the shell namespace” I showed you how to iterate items in the shell namespace. In that article you learned about pidls (a pointer to an item ID list). A pidl is used to describe an item in the shell namespace. In this article you will learn how to use a pidl to obtain information about a particular shell item. Specifically, you will learn how to use the SHGetFileInfo() function to obtain the following:

  1. Large and small icons for shell items

  2. The overlay icon for shared items

  3. The overlay icon for shortcuts

  4. The display name of shell items

  5. The type description of shell items

The functions and constants discussed in this article are defined in SHELLAPI.H and SHLOBJ.H so you will need to include those headers in any application that uses the techniques presented here.

The shell is a moving target

Before I get into the explanations of how to go about obtaining the details of shell items I need to warn you of some issues that you may encounter. Specifically, you need to be aware that the Windows shell will vary widely from one machine to another.

The shell version is largely determined by the version of Microsoft Internet Explorer installed on a particular system. As you might assume, later versions of the shell have features not found in earlier versions of the shell. For the most part this is determined by the version of SHELL32.DLL. Further complicating the problem is the fact that the various flavors of Windows have different shells. For example, shell operations that work on Windows 95 may not work on Windows 98 or Windows NT. In fact, the shell can even vary between different versions of the same operating system. For example, a machine running Windows NT with Service Pack 1 installed may behave differently than an NT machine with a different Service Pack installed. Considering all of this, you can figure out that there are dozens of combinations of operating system and shell version. The best you can do is to write your shell code and know that it may not work properly on all systems. This situation is not quite as dire as it might sound, at least for the topics I will discuss in this article, but let me give you an example.

It is fairly obvious that Windows Explorer allows you to share drives or individual folders. A shared drive or folder is depicted in Explorer with a hand icon. This icon is an overlay icon drawn over the regular icon for the shared folder. The same is true for shortcuts; the shortcut overlay icon is drawn on top of the regular icon for a particular item. This article will explain how to handle overlay icons. My tests show that the overlays work fine on Win9x systems that I have tested on, and on my NT Workstation machine. However, the overlay icons don’t work properly on my NT Server machine with the IE Desktop Update installed. MSDN has an article on this subject and that article says that the problem is “as designed.” See the sidebar entitled, “Overlays for all occasions” for information on how to work around this feature of the shell.

The point of all of this is that you need to be aware that what we think of as “the shell” will vary widely on different machines.

Getting item icons

Perhaps one of the more interesting details you can obtain for a shell item is the icon associated with that item. The icons for shell items are obtained from the system image list. Before I explain how to get the icons let me explain how the system image list works.

The system image list and TImageList

The system image list is a list of icons maintained by the operating system. To obtain an item in the system image list, you first associate a TImageList with the system image list. I’ll explain how to do that later in this section. The interaction between the system image list and the TImageList varies between operating systems. Under Win9x, the entire system image list is copied to the TImageList. Under NT, however, each process gets a copy of the system image list. Icons are not added to the TImageList until they are requested by the application. For example, when you first connect the system image list to a TImageList, the TImageList will contain from one to five icons (again, depending on the OS and shell version). One image that will always be in the list is the default folder icon. Other images will include the icon for a folder in the open state, the standard Windows icon for a document and executable file, and the overlay icons for shared folders and shortcuts. As you enumerate the shell and request icons for shell items, any new icons that are needed are copied to the TImageList. You can witness this on an NT machine by running this article’s example program and clicking the Show Images button. You should see new images added to the image list each time you enumerate a new folder.

It is not vital that you understand how the connection between the system image list and a TImageList works, but I’ve included this explanation in case you examine the TImageList and notice the surprising lack of icons on NT.

Hooking into the system image list

Hooking into the system image list is trivial. Once you have an instance of TImageList you can easily associate it with the system image list. The example program for this article displays shell items in a list view. First I set up the list view’s image lists:

ListView1->SmallImages = 
  new TImageList(this);
ListView1->LargeImages = 
  new TImageList(this);
ListView1->
  SmallImages->ShareImages = true;
ListView1->
  LargeImages->ShareImages = true;

I first allocate memory for the SmallImages and LargeImages properties of the list view. Next I set the ShareImages property of each image list to true. This step is important because it keeps the system image list intact. When ShareImages is true, the handle of the underlying image list will not be deleted when the TImageList is destroyed. The list view’s image lists will be “borrowing” the system image list handle and, as such, should not destroy that handle when the TImageList is deleted. Failure to set ShareImages to true could corrupt the system image list on Win9x platforms.

Next I hook the list view’s SmallImages and LargeImages properties to the system image list. This is done by calling the SHGetFileInfo() function:

SHFILEINFO fi;
ListView1->SmallImages->Handle =
  SHGetFileInfo("", 0, &fi, sizeof(fi),
  SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
ListView1->LargeImages->Handle =
  SHGetFileInfo("", 0, &fi, sizeof(fi), 
  SHGFI_SYSICONINDEX | SHGFI_LARGEICON);

SHGetFileInfo() takes five parameters. The first parameter is the path and filename for the file or folder for which you wish to obtain shell information. In this case I pass an empty string because I am only getting the image list handle. The second parameter is used to specify the file attributes you are requesting. I don’t need the attributes so I pass 0 for this parameter. The third and fourth parameters are a pointer to a SHFILEINFO structure and the size of the structure. This structure is filled out with the requested information. In this case, I’m not interested in the information returned in the structure but I still have to pass the pointer and size.

Finally, we get to the final parameter. This parameter is used to specify a set of flags that determine the type of information requested. In the first call to SHGetFileInfo() I pass the SHGFI_SYSICONINDEX and SHGFI_SMALLICON flags. When SHGetFileInfo() is called with these flags, it will return a handle to the system image list that contains the shell’s small icons. I assign the return value to the Handle property of the list view’s SmallIcons property. This associates the system image list with the list view’s small icon TImageList. The next call to SHGetFileInfo() passes the SHGFI_LARGEICON flag to obtain the large icons.

At this point the list view can use the icons from the system image list.

Getting the icon for an item

Finally we get to the point where we are ready to retrieve the icon for a particular item in the shell. This is done by calling SHGetFileInfo(). There are two ways you can go about getting the icon. One way is to pass the path and filename for the item to SHGetFileInfo():

DWORD Flags = 
  SHGFI_SYSICONINDEX | SHGFI_ATTRIBUTES;
SHFILEINFO fi;
DWORD Result = SHGetFileInfo(
  “c:\\test.txt”, 0, &fi, 
  sizeof(fi), Flags);
if (Result != 0) // success

The second way is to pass a pidl in the first parameter. Since the first parameter takes a char* you will need to cast the pidl:

DWORD Flags = SHGFI_PIDL | 
  SHGFI_SYSICONINDEX | SHGFI_ATTRIBUTES;
SHFILEINFO fi;
DWORD Result = SHGetFileInfo(
  (char*)pidl, 0, &fi, 
  sizeof(fi), Flags);
if (Result != 0) // success

The first method certainly seems easier, but remember that not all shell items are part of the file system. You must use a pidl, for example, to obtain the icons for items in the Control Panel, in the Network Neighborhood, and in other special shell folders. Also, if you are enumerating the shell you already have a pidl so the hard work is already done by the time you get around to calling SHGetFileInfo(). It is important to remember that the pidl passed to SHGetFileInfo() must be a fully qualified pidl (relative to the Desktop folder).

Note the flags I set for each of the methods presented. The SHGFI_ATTRIBUTES flag is not used in this code but it is required to get the overlay icon as explained in the next section.

When SHGetFileInfo() returns, the iIcon member of the SHFILEINFO structure will contain the index of the item’s icon in the system image list. Using the icon in a list view is simple:

TListItem* item = 
  ListView1->Items->Add();
item->ImageIndex = fi.iIcon;

I simply assign the icon index contained in the iIcon member to the ImageIndex property of the list view.You could, of course, access the icon in the image list directly. If, for example, you were creating an owner-drawn combo box that displays shell items, you would have a dedicated TImageList to hold the shell icons. You would then use the methods of TImageList to extract and draw the icon.

Setting the overlay icons

Setting the overlay icon for a shell item is relatively easy. The index for the overlay icon that indicates a shared folder is 0, and the index for the shortcut overlay icon is 1. Given that, you only need to check to see whether an item is shared or a shortcut and assign the appropriate image accordingly. The dwAttributes member of the SHFILEINFO structure contains the shell item’s attributes. After you call SHGetFileInfo() you can set the overlay icon with this code:

if ((fi.dwAttributes & SFGAO_SHARE)
     == SFGAO_SHARE)
  item->OverlayIndex = 0;
else if ((fi.dwAttributes & SFGAO_LINK)
     == SFGAO_LINK)
  item->OverlayIndex = 1;
else
  item->OverlayIndex = -1;

This code builds on the code you saw in the previous section. I simply set the TListItem’s OverlayIndex property to 0 if the item is shared, to 1 if the item is a shortcut, or to –1 for all other cases. The list view will do the rest by painting the overlay icon over the regular icon for the shell item.

There is one aspect of TListView and overlay icons that you need to be aware of. For some reason, the OverlayIndex property is ignored when the list view is a virtual list view. I suspect this is a bug in the TListView code. If you are using a virtual list view and want overlay icons, you will have to catch the WM_PAINT message and drawing each item yourself.

Getting the display name and type

Getting the display name and type description of a shell item can be combined into a single operation. Here is the code:

DWORD Flags = SHGFI_PIDL |
  SHGFI_DISPLAYNAME | SHGFI_TYPENAME;
SHFILEINFO fi;
DWORD Result = SHGetFileInfo(
  (char*)pidl, 0, &fi, sizeof(fi), Flags);
String typeDescription;
if (Result != 0) {
  item->Caption = fi.szDisplayName;
  typeDescription = fi.szTypeName
}

When the SHGFI_DISPLAYNAME flag is set, the szDisplayName member of SHFILEINFO will contain the display name of the item. The value contained in szDisplayName can be used for the caption of the list view item. Likewise, when the SHGFI_TYPENAME flag is set, the szTypeName member will contain the type description of the item. You could save this description and use it to display the file’s type if the list view is in report mode.

Earlier I showed you how to get the icon for a shell item. There I set flags to obtain just the icon. In reality, I would combine the code presented in that section with the code presented here and make just one call to SHGetFileInfo().

There is one aspect of SHGetFileInfo() that you should be aware of. Specifically, SHGetFileInfo() takes a relatively long time to execute. If you are enumerating a folder containing thousands of items, the call to SHGetFileInfo() will be the most time consuming part of the enumeration. I have worked around this in my own code by using a virtual list view and by calling SHGetFileInfo() only when an item needs to be displayed (virtual list views were discussed last month in the article, “Faster Updates with Virtual List Views”).

Conclusion

In this article I showed you how to obtain information about a shell item using the SHGetFileInfo() function. The example program for this article allows you to enumerate a folder in the file system. The results are shown in a list view, complete with the proper icons. The example program also has a second form that can be used to view the system image list. The source for the program’s main form is shown in Listing A. I don’t show the main form’s header or the code for secondary form to save space. I should point out that the example uses an undocumented shell function called ILCombine(). As its name implies, this function combines two pidls and returns a new pidl containing the result. A pointer to this function is obtained using GetProcAddress() because the C++Builder linker does not support importing functions by ordinal. For more information on undocumented shell functions I suggest you visit the Web site at www.geocities.com/siliconvalley/4942.

Listing A: MAINU.CPP

#include <vcl.h>
#pragma hdrstop

#include <shellapi.h>
#include <shlobj.h>
#include "MainU.h"
#include "ViewImagesU.h"
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
HINSTANCE Shell32Inst;
typedef LPITEMIDLIST (__stdcall *pILCombine)
  (LPITEMIDLIST, LPITEMIDLIST);
pILCombine ILCombine;
LPSHELLFOLDER DesktopFolder;

int __stdcall ListViewCompareFunc(LPARAM lParam1,
  LPARAM lParam2, LPARAM lParam)
{
  TListItem* Item1 = (TListItem*)lParam1;
  TListItem* Item2 = (TListItem*)lParam2;
  // Use CompareIDs to sort the list view.
  return (short)DesktopFolder->CompareIDs(0,
    (LPITEMIDLIST)Item1->Data,
    (LPITEMIDLIST)Item2->Data);
}

__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
}

void __fastcall TForm1::FormCreate(TObject *Sender)
{
  // Set up the image lists.
  ListView1->SmallImages = new TImageList(this);
  ListView1->LargeImages = new TImageList(this);
  ListView1->SmallImages->ShareImages = true;
  ListView1->LargeImages->ShareImages = true;
  SHFILEINFO fi;
  ListView1->SmallImages->Handle =
    SHGetFileInfo("", 0, &fi, sizeof(fi),
      SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
  ListView1->LargeImages->Handle =
    SHGetFileInfo("", 0, &fi, sizeof(fi),
      SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
  // Load SHELL32.DLL and get a pointer to the
  // undocumented ILCombine function.
  Shell32Inst = LoadLibrary("shell32.dll");
  if (Shell32Inst)
    ILCombine = (pILCombine)GetProcAddress(
      Shell32Inst, (const char*)25);
}

void __fastcall TForm1::FormDestroy(TObject *Sender)
{
  FreeLibrary(Shell32Inst);
}

void __fastcall TForm1::EnumBtnClick(TObject *Sender)
{
  ListView1->Cursor = crHourGlass;
  ListView1->Perform(WM_SETREDRAW, 0, 0);
  FreePidls();
  ListView1->Items->Clear();

  LPSHELLFOLDER ParentFolder;
  LPITEMIDLIST ParentPidl;
  LPITEMIDLIST ChildPidl;
  LPITEMIDLIST fqPidl;
  DWORD Eaten;
  wchar_t Path[MAX_PATH];
  // Get an IShellFolder for the path in the edit.
  StringToWideChar(
    StartPathEdit->Text, Path, MAX_PATH);
  DWORD Result = DesktopFolder->ParseDisplayName(
    Handle, 0, Path, &Eaten, &ParentPidl, 0);
  Result = DesktopFolder->BindToObject(ParentPidl,
    0, IID_IShellFolder, (void**)&ParentFolder);
  if (Result != NOERROR)
    return;
  LPENUMIDLIST Enum;
  // Set enumeration flags to get all items.
  Result = ParentFolder->EnumObjects(Handle,
    SHCONTF_FOLDERS | SHCONTF_NONFOLDERS |
    SHCONTF_INCLUDEHIDDEN, &Enum);
  if (Result != NOERROR)
    return;
  // Get the pidl for the first item in the folder.
  LPMALLOC Malloc;
  SHGetMalloc(&Malloc);
  Result = Enum->Next(1, &ChildPidl, 0);
  while (Result != S_FALSE) {
    // If result is something other than
    // NOERROR then some error occured.
    if (Result != NOERROR)
      break;
    TListItem* Item = ListView1->Items->Add();
    fqPidl = ILCombine(ParentPidl, ChildPidl);
    SetItemAttributes(Item, fqPidl);
    // Free the memory for the pidl returned
    // by the Enum->Next() function.
    Malloc->Free(ChildPidl);
    // Get a pidl for the next item in the folder.
    Result = Enum->Next(1, &ChildPidl, 0);
  }
  // Sort the list view and enable drawing.
  ListView1->CustomSort(ListViewCompareFunc, 0);
  ListView1->Perform(WM_SETREDRAW, 1, 0);
  // Clean up.
  Malloc->Free(ParentPidl);
  Enum->Release();
  DesktopFolder->Release();
  ParentFolder->Release();
  Malloc->Release();
  ListView1->Cursor = crDefault;
}

void TForm1::SetItemAttributes(TListItem* item,
  LPITEMIDLIST pidl)
{
  DWORD Flags = SHGFI_PIDL |
    SHGFI_SYSICONINDEX | SHGFI_DISPLAYNAME |
    SHGFI_ATTRIBUTES;
  SHFILEINFO fi;
  DWORD Result = SHGetFileInfo(
    (char*)pidl, 0, &fi, sizeof(fi), Flags);
  // Error getting the info.
  if (Result == 0)
    return;
  // Set the image index with the value
  // returned by Windows.
  item->ImageIndex = fi.iIcon;
  // Save the pidl in the Data property of the
  // item. We'll need it to sort the list view.
  item->Data = pidl;
  // Set the Cut property based on the
  // ghosted attribute.
  item->Cut = (fi.dwAttributes & SFGAO_GHOSTED)
       == SFGAO_GHOSTED;
  // Set the caption of the item.
  item->Caption = fi.szDisplayName;
  // Set the overlay icon index.
  if ((fi.dwAttributes & SFGAO_SHARE)
       == SFGAO_SHARE)
    item->OverlayIndex = 0;
  else if ((fi.dwAttributes & SFGAO_LINK)
       == SFGAO_LINK)
    item->OverlayIndex = 1;
  else
    item->OverlayIndex = -1;
}

void TForm1::FreePidls()
{
  LPMALLOC Malloc;
  SHGetMalloc(&Malloc);
  for (int i=0;i<ListView1->Items->Count;i++)
    Malloc->Free(ListView1->Items->Item[i]->Data);
  Malloc->Release();
}

void __fastcall TForm1::ShowIconsBtnClick(TObject *Sender)
{
  TViewImagesForm* form = new TViewImagesForm(this);
  form->SmallImages = ListView1->SmallImages;
  form->LargeImages = ListView1->LargeImages;
  form->ShowModal();
  delete form;
}