Using Bitmaps
WinWatch needs to display a selected bitmap in a picture box. There are lots of ways to do this, but we’re going to do it the easy way. The reason it’s easy is because I’ve hidden all the ugly details in procedures that we’ll look at later. Here’s the easy part:
Sub ShowBitmap(ByVal hMod As Long, sBitmap As String)
With pbResource
Dim hPal As Long, hPal2 As Long
‘ Convert resource into bitmap handle
hResourceCur = LoadBitmapPalette(hMod, sBitmap, hPal)
If hResourceCur = hNull Then
pbResource.Print “Can’t load bitmap: “ & sCrLf & sCrLf & _
WordWrap(ApiError(Err.LastDllError), 25)
Exit Sub
End If
‘ Convert hBitmap to Picture (clip anything larger than picture box)
.Picture = BitmapToPicture(hResourceCur, hPal)
‘ Set the form palette to use this picture’s palette
Palette = .Picture
‘ Make sure palette is realized
Refresh
DoEvents
‘ Draw the palette
DrawPalette pbResource, hPal, .Width, .Height * 0.1, 0, .Height * 0.9
‘ Record the type for cleanup
ordResourceLast = RT_BITMAP
End With
End Sub
The first step is to convert the resource into a bitmap handle. Remember, at this point the sBitmap string will be either the name of a bitmap resource (MyBitmap)
The Zen of Windows Memory Management
Normally, the Windows Way of managing memory is the least of your worries. Dim, ReDim, Private, Public, and Static do it all for you, and, for the most part, transparently. You don’t ordinarily use the Windows memory functions directly because they deal with pointers, and Visual Basic doesn’t do pointers. But there are exceptions to every rule, and loading unknown resources is one of them.
Here’s a 30-second introduction to the rarely needed art of Windows global memory management:
-
Get a handle to a global memory block. You can create a generic one with GlobalAlloc, or you can create a resource block by calling LoadResource on the value returned by FindResource. Use resource functions instead of global memory functions on resource blocks. You have several choices for how you want the memory to be allocated, but I’ll leave you to the documentation.
-
Measure the memory with GlobalSize (or remember its size if you created it with GlobalAlloc). If it’s a resource, use SizeofResource. If you don’t know the size of a global memory block, there won’t be much you can safely do with it in Visual Basic.
-
Lock the memory handle with GlobalLock (or LockResource if it’s a resource handle). This means that you ask the memory manager to let you play with the memory for a while. If your request is granted, you’ll be given a pointer to the memory and other users will be locked out of it.
-
Have your way with the pointer and the stuff it points to. This is the tricky part because, in Visual Basic, a pointer is like a handle—all you can do with it is pass it on to another API function. For example, you can use CopyMemory to copy the memory of a pointer to a Visual Basic string. (You must know the size to do this; see step 2.) If you dare, you can modify the string and copy it back to the pointer (again using CopyMemory). If you change the size of the data, you’ll need to call GlobalReAlloc to change the size of the original block before copying. You can find other API functions to pass your memory pointers to (such as sndPlaySound, described later).
-
When you’re done with the block, use GlobalUnlock to release the pointer back to the system. Some kinds of global memory don’t need to be unlocked, but it does no harm and might save you from a horrible fate if you mix up your fixed and movable memory. I always
unlock. The exception to this rule is that you don’t need to unlock resources. In fact, the Win32 UnlockResource function is nothing more than a C macro that does nothing. If you like to be consistent, you can call the do-nothing UnlockResource function in the PICTOOL.BAS module.
-
Free the memory if you created it; leave it alone if Windows created
it. Use GlobalFree to free memory you created with GlobalAlloc; use FreeResource to free memory loaded with LoadResource.
or, more likely, a numeric resource ID converted into a string (#01001). In days of old, we would have read the resource with the simpler LoadBitmap API function, but LoadBitmap only loads device dependent bitmaps. The new LoadImage function loads any bitmap, including device independent bitmaps (DIB), and it can load them from files as well as from resources. It’s also more flexible for loading icons and cursors, as we’ll see later. Unfortunately, when LoadImage loads a bitmap with a palette, it throws away any accompanying palette. I had to write my own LoadBitmapPalette function to get both. But ignore the palette issue for now.
The hResourceCur returned by LoadBitmapPalette is actually a bitmap handle. At this point, I could work directly on the handle. I could select the bitmap into the DC of the picture box. I could create a memory DC, select the bitmap into it, and then blit the memory DC to the DC of the picture box. I demonstrated some of these GDI choices in Chapter 7. But what I really want to do with that Windows-style handle is turn it into a Visual Basic–style picture. Once I have a picture, I can assign it to various Picture properties or blit it with the PaintPicture method.
The BitmapToPicture function does the conversion. Again, I’m ignoring the palette issue until after I check out the COM magic that makes picture conversion possible.