Using the file information wrapper class


The SHGetFileInfo function is complicated, in the typical API style. In other words, it’s un-Basic and needs some wrappers. I wrote my wrappers as a class named CFileInfo. As usual, let’s watch this class in action before we start to examine how it works.


The Windows Interface Tricks application (TWHIZ.VBP) demonstrates the information you can get and the operations you can perform on files. Figure 11-10 shows a frozen screen shot of the program, but to really get a feel for what you can do, you’ll have to run it. There’s a lot going on in this application; you’ll need to study the Win32 API Help to fully understand the functions.



Figure 11-10. File information and operations.


The following code from the program shows how to get the information displayed for the source file, including large and small icons in Image controls:

Private Sub NewSource()
With fiSrc
Dim s As String, sOld As String
On Error GoTo FailNewSource
sOld = fiSrc
‘ Assign a file name to a file information object
fiSrc = txtSrc.Text
‘ Get back all the information about the file
s = s & “Display name: “ & .DisplayName & sCrLf
s = s & “Type name: “ & .TypeName & sCrLf
s = s & “Length: “ & .Length & “ bytes” & sCrLf
s = s & “Created: “ & .Created & sCrLf
s = s & “Last modified: “ & .Modified & sCrLf
s = s & “Last accessed: “ & .Accessed & sCrLf
lblSrc.Caption = s
Set imgLIcon.Picture = .ShellIcon()
Set imgSIcon.Picture = .SmallIcon()
Set imgLIconSel.Picture = .ShellIcon(SHGFI_SELECTED)
Set imgSIconSel.Picture = .SmallIcon(SHGFI_SELECTED)
Set imgLIconLink.Picture = .ShellIcon(SHGFI_LINKOVERLAY)
Set imgSIconLink.Picture = .SmallIcon(SHGFI_LINKOVERLAY)
§

That example shows one side of the CFileInfo class, but the default Item property is actually a Variant. You can pass it a string file name as shown above, or you can pass it an integer representing an item in the shell name space. The shell name space is the organization of icons you see in the Explorer under Windows 95 and Windows NT. The idea is to represent everything used by your computer, not just files, in a hierarchical structure. I’ll take a closer look at this new way of organizing data shortly.


For now, notice the UpDown control (upper right corner of the screen in Figure 11-10) that lets you select a special folder location. You can cycle through constants representing special folder locations. The most interesting are CSIDL­_BITBUCKET (Recycle Bin), CSIDL_DESKTOP (Desktop), and CSIDL_DRIVES (My Computer). The following code gets the information for a special folder location:

Private Sub NewSpecialFolder(ByVal iInc As Long)
Do
On Error Resume Next
fiFolder = udSpecLoc ‘ Folder to CFileInfo object
If Err = 0 Then Exit Do
udSpecLoc.Value = udSpecLoc + iInc ‘ Skip missing numbers
‘ Wrap property doesn’t work on assignment
If udSpecLoc = udSpecLoc.Min Then udSpecLoc = udSpecLoc.Max
If udSpecLoc = udSpecLoc.Max Then udSpecLoc = udSpecLoc.Min
Loop
lblSpecLoc.Caption = “Special folder: “ & fiFolder.DisplayName
imgLSpecLoc = fiFolder.ShellIcon
imgSSpecLoc = fiFolder.SmallIcon
End Sub

Some values in the range of special folder constants are invalid, so the code has to cycle through until it finds an acceptable constant.


You can also get information from a CFileInfo object by assigning it a data structure called a PIDL, which you receive from the shell name space. A PIDL is actually a pointer stored in a Long. But that’s another story—unfortunately, a horror story.

Wrapping up file information


Most of the work of creating a CFileInfo object (FILEINFO.CLS) is done in the Item property Let procedure of the class. Item is the default property so that you can create a new object by assigning a file name, a special folder, or a PIDL to the object. The code in this procedure is a wrapper for the FindFirstFile and SHGetFileInfo API functions. It will get all the information it can about the file and store it in the following private variables:

Private Enum EItemState
eisNotCreated
eisFile ‘ File or directory
eisDrive ‘ Drive
eisID ‘ PIDL passed to us
eisFolder ‘ PIDL created by us from special folder
End Enum
Private eis As EItemState ‘ How object was created
Private vItem As Variant ‘ File name or PIDL
Private shfi As SHFILEINFO ‘ Info from SHGetFileInfo
Private fd As WIN32_FIND_DATA ‘ Info from FindFirstFile
Private afAttr As Long ‘ File attributes
Private afOption As Long ‘ Options for SHGetFileInfo

SHGetFileInfo is one of those multipurpose functions that tries to do too many things at once, but ends up not doing enough. You pass it a structure and any combination of 15 different flags. It looks at all the flags to determine what to return in the structure. Even with all of that, it doesn’t return all the ­information you might need and you have to call FindFirstFile to get additional information.


Here’s the code to analyze what kind of item is being created and to set up all the necessary options and make the appropriate API calls:

Property Let Item(vItemA As Variant)
Dim h As Long, f As Long, af As Long
Destroy ‘ Clear any previous assignment
If VarType(vItemA) = vbString Then
‘ String item is a file, directory, or drive
If Len(vItemA) <= 3 And Mid$(vItemA, 2, 1) = “:” Then
‘ Must be drive, get attributes
afAttr = 0: afOption = 0
Else
‘ No terminating backslashes
MUtility.DenormalizePath vItemA
‘ For file, get information in advance
h = FindFirstFile(vItemA, fd)
If h = hInvalid Then ApiRaise Err.LastDllError
FindClose h
afAttr = fd.dwFileAttributes
afOption = SHGFI_USEFILEATTRIBUTES
End If
eis = eisFile
af = afOption And (Not SHGFI_PIDL) Or _
SHGFI_DISPLAYNAME Or SHGFI_TYPENAME
f = SHGetFileInfo(vItemA, afAttr, shfi, LenB(shfi), af)
Else
‘ Integer item is a special folder constant or pidl
If vItemA < 50 Then
‘ Turn special folder location into a pidl
Dim pidl As Long
SHGetSpecialFolderLocation 0, CLng(vItemA), pidl
vItemA = pidl
eis = eisFolder
Else
eis = eisID
pidl = vItemA
End If
‘ For special folders or other PIDLs, everything comes from system
afAttr = 0: afOption = 0
‘ Get item ID pointer, but don’t use attributes
af = SHGFI_PIDL Or SHGFI_DISPLAYNAME Or _
SHGFI_TYPENAME
f = SHGetItemInfo(pidl, afAttr, shfi, Len(shfi), af)
End If
If f Then
vItem = vItemA
Else
eis = eisNotCreated
End If
End Property

This code follows several different paths, but ultimately it comes down to either calling SHGetFileInfo for a file object or calling SHGetItemInfo for a PIDL object. These are both aliases for the SHGetFileInfo function, but the Windows API type library offers versions that take a different type for the first parameter. In C++, a string is a pointer and a PIDL is a pointer, so with the appropriate typecasting, you can pass either one in the same parameter. That’s not such a good idea in Visual Basic (and I don’t think much of it in C++ either).


The GetFileItemInfo function takes a Variant parameter and determines from the Variant type which API alias to call. Most of the CFileInfo methods call GetFile­ItemInfo to get additional information about a file. For example, here’s the SmallIcon method:

Function SmallIcon(Optional afOverlay As Long = 0) As Picture
Dim shfiT As SHFILEINFO
If eis = eisNotCreated Then Exit Function
' Filter out any invalid flags -- only overlays allowed
afOverlay = afOverlay And (SHGFI_LINKOVERLAY Or SHGFI_SELECTED _
Or SHGFI_OPENICON)
' Add in standard and small icon flags
afOverlay = afOverlay Or afOption Or SHGFI_ICON Or SHGFI_SMALLICON
GetFileItemInfo vItem, shfiT, afOverlay, afAttr
Set SmallIcon = MPicTool.IconToPicture(shfiT.hIcon)
End Function

The afOverlay parameter allows you the option of specifying whether the standard icon will appear selected, as a shortcut, or open. The open overlay isn’t really an overlay; it’s a completely different icon indicating that a folder is open. The open flag is ignored for files, which don’t have a separate icon for the open state.

You can probably guess the implementation of the Length, Created, Modified, and Accessed methods, which use data received from the call to FindFirstFile. There’s a lot more detail to the CFileInfo class. For example, it also handles drives and supplies appropriate drive data in properties—but I’ll leave you to struggle through that yourself.

SHGetFileInfo retrieves additional data not used in the CFileInfo class. For example, large and small icons used by the system are maintained in two internal ImageLists. Unfortunately, the Visual Basic ImageList control doesn’t give you a way to use them. It’s possible to iterate through the system ImageList control directly by using ImageList API functions with SHGetFileInfo. This technique is illustrated in the Windows Interface Tricks application. It turned out to be a dead end, however; I couldn’t find anything useful to do with system ImageLists in Visual Basic that couldn’t be done more easily without them.