Back to icons in WinWatch


Since WinWatch is getting its icons from the resources of another program, it has to do things the hard way. It handles single icons (resource RT_ICON) differently than it handles icon groups (RT_GROUP_ICON). An icon group is comparable to an icon file containing multiple images.


It’s a simple matter to show a single icon: just load the icon handle with Load­Icon, and then call DrawIcon to display it. When you’re done, call DestroyIcon to release the resource. That’s how I handled icons in early versions of Win­Watch, but eventually I found a better way. Here’s the ShowIcon code:

Sub ShowIcon(ByVal hMod As Long, sIcon As String)
BugAssert (hMod <> hNull) And (sIcon <> sEmpty)

‘ Load icon resource
hResourceCur = LoadImage(hMod, sIcon, IMAGE_ICON, 0, 0, 0)
With pbResource
If hResourceCur <> hNull Then
‘ Convert icon handle to Picture
Dim pic As New StdPicture
Set pic = IconToPicture(hResourceCur)
pbResource.PaintPicture pic, 0, 0
ordResourceLast = RT_ICON
Else
pbResource.Print “Can’t display icon: “ & sCrLf & sCrLf & _
WordWrap(ApiError(Err.LastDllError), 25)
End If
End With

End Sub

The code starts by calling the LoadImage function with arguments that indicate that the icon should be displayed at its actual image size. All the single icon resources I’ve encountered in WinWatch have been 32 pixels square. Once the icon resource is converted to an icon handle, we call IconToPicture (which looks a lot like BitmapToPicture) to convert it to a picture. From there we can draw the icon with PaintPicture. The PaintPicture method knows the difference between an icon and a bitmap, and draws the icon transparently.

Icon groups usually contain several different icons of different sizes and possibly different resolutions. The system is supposed to go through the list of images and pick the image that best matches the current display adapter and context. The StdPicture class doesn’t know anything about multiple images in the same icon file, so I’ll have to use raw GDI functions to process multiple icons. Worse, there’s no good API function to iterate through the images in an icon resource. You have to do it the hard way by reading semi-random bytes from semi-random offsets in the resource memory.

You can use icons to create animation. The technique works much like the CPictureGlass class described in Chapter 7 except that Windows does all the dirty work. Just create an icon of the desired size and draw it wherever you want. The problem is that you’ll seldom want to do animation based on the standard icon sizes, but it’s difficult to get icons of arbitrary size. Many icon tools know how to create only standard sizes. The icon editor in Visual C++ 5.0 can create an icon of arbitrary size, but you must make sure that your 56 by 45 pixel icon image is the only one in the resulting icon file. The icon editor will try to give you a second icon image of standard size, but you should delete it. You can’t load your arbitrary-sized icon into a Picture with Visual Basic’s LoadPicture or with my IconToPicture, but you can load it and get an icon handle with LoadImage. Once you’ve got the handle, you can draw the transparent image with DrawIconEx.


Reading the resource data works the same as the ShowData function discussed in “Using Data Resources,” page 452. Once you get the memory, there are two possible ways to process it. One way is to use the structures described in obscure parts of the Windows documentation. Unfortunately, these structures are connected dynamically. There’s a header structure containing information about the icon file, including how many entries there are. This is followed by a separate structure for each entry, but you don’t know how many entries there are until run time. You could theoretically write a UDT to represent the maximum size (like LOGPALETTE256 and BITMAPINFO256 described earlier), but the maximum size is unknown. Processing dynamic structures is not Visual Basic’s strong point, but you do it by processing the data as a blob of bytes. Study the structures to find the offsets of the data you need, and then read the data with the blob functions introduced in “Reading and Writing Blobs” in Chapter 5.


It’s an ugly business, so instead of showing the whole thing, I’ll just
show pseudocode up to the interesting point. You can read the rest of it in WINWATCH.FRM.

Sub ShowIcons(ByVal hMod As Long, sIcon As String)
‘ Find the resource
§
‘ Allocate memory block, get size, get pointer, and allocate array
§
‘ Get image count and set up first entry
§
For i = 0 To cImage - 1
‘ Get size and colors of current icon in write to string s
§
‘ Find, load, size, allocate, and copy entry
§
‘ Real code begins here

‘ Create an icon from resource data
hIcon = CreateIconFromResource(abEntry(0), cRes, True, &H30000)
‘ Draw icon and print description
Call DrawIconEx(pbResource.hDC, 0, pbResource.CurrentY, hIcon, _
dxIcon, dyIcon, 0, hNull, DI_NORMAL)
pbResource.Print s
‘ Move to next entry
pbResource.CurrentY = pbResource.CurrentY + dyIcon
pbResource.CurrentX = 75
iImage = iImage + cEntrySize
Next
pbResource.ScaleMode = vbTwips
hResourceCur = hIcon
ordResourceLast = RT_ICON

End Sub

At the point where we pick up the story, the call to the CreateIconFromResource API function, you have a blob named abEntry containing one icon block of length cRes. The True parameter means this is an icon (False means cursor) and &H30000 is a random version number. Once you have the icon handle, the most accurate way to draw it is with DrawIconEx, which knows how to draw an icon of any size at any location. (The DrawIcon function assumes that all icons are the same size.) After drawing the icon and printing information about its size and the number of colors it supports, you move to the next icon image. When the last icon is drawn, you restore the ScaleMode property to twips. Often, when working in GDI mode, it’s easiest to change the ScaleMode to pixels to perform your calculations. Just be sure to change it back when you’re done.

All that fancy code to extract resources from executable files is wasted on icons. Windows provides the ExtractIcon and ExtractIconEx API functions to do it all for you (although it lacks corresponding ExtractBitmap and ExtractCursor functions). If you simply need icons, do it the easy way. The following code loops through all the icons in a program:

ExtractIcon

c = ExtractIcon(App.hInstance, sExe, -1)
For i = 0 To c - 1
hIcon = ExtractIcon(App.hInstance, sExe, i)
‘ Do something with icon here
Loop

ExtractIconEx is similar, but it can read all the large and small icons into an array.