So You Have a Bunch of Resources -- Now What Do You Do?
by Dianne Hackborn, SW Engineer / Framework
A few months ago I wrote about QuickRes, our new tool for viewing and editing program resources of all types. If you haven't used it yet, the current version (updated since that last Newsletter article) can be found at:
x86: QuickRes-x86-20010126.zip
PPC: (not available)
The question I've seen asked most often about resources is how to use them in an application, and in particular how to use a bitmap stored in a resource to create a BBitmap that can be drawn into a view. Conveniently, the Translation Kit includes two functions that make this easy to accomplish:
While they are very convenient, there are some issues and limitations related to these functions that you should be aware of:BBitmap* myBitmap = BTranslationUtils::GetBitmap('type', "name"); BBitmap* myBitmap = BTranslationUtils::GetBitmap('type', id);
BResourceSet is a class I wrote to address the limitations of GetBitmap(). This class is a wrapper around one or more BResources objects, providing a similar API along with additional methods for directly retrieving BBitmap, BCursor, and BMessage objects from the resource data.
You can find the source code here: BResourceSet.zip. Also included is a sample application, LoadResources, that demonstrates using the class within an add-on to display an icon.
Using BResourceSet is very simple, requiring three steps:
1. Create a single BResourceSet instance that will provide access to all your image's resources.
2. Call the AddResources() methods one or more times to point this instance at the resource data it will load.
3. Use FindBitmap(), FindCursor(), and FindMessage() to retrieve data from your resources.
The BResourceSet class keeps a cache of all resource data it has found. For example, the first time you request a bitmap, it will load the bitmap data from its resources, create and store the BBitmap object, and return a pointer to you. Every time thereafter it will immediately find the existing BBitmap object and return the same one to you, without further effort. All objects are returned as const pointers -- BResourceSet will free the data for you when it is deleted, so you shouldn't delete the objects yourself.
There are three pieces to the BResourceSet API: telling it where to look for resources, loading resources, and generating objects from resources. The first two are public; the last is protected, to be used by subclasses to extend the data formats that can be handled.
You tell BResourceSet where to find its resource by calling the AddResources() methods. This method has two flavors: the first is given an actual BResources object, which BResourceSet then takes ownership of and manages. The second provides an address of some code in your program, and creates a new BResources object for the image at that location. This second form is particularly useful for add-ons, as it can be complicated to track back to where your add-on lives on disk (and thus create a BResources object on the file).
Two additional functions are available, AddDirectory() and AddEnvDirectory(). These tell the BResourceSet to also look in the given directories when resource data is requested by name.
The core method for finding resource data is FindResource(). This works identically to the BResources implementation, returning the raw resource data found. In addition, you can use the FindBitmap(), FindCursor(), and FindMessage() to return objects of these types created from that raw resource data. The BResourceSet class manages all this data, so you should never free it yourself, nor use it after deleting the BResourceSet instance that it came from.
Finally, subclasses can implement the functions GenerateBitmap(), GenerateCursor(), and GenerateMessage() to create an object of the respective type from raw resource data. The default implementation of these is typically all you need.
In the case of bitmaps, BResourceSet has built-in code for instantiating bitmaps from archived messages and PNG data streams. This means that you normally won't need to link against the Translation Kit, though you will need to link with the static libraries libpng.a and libz.a for the PNG decoding code. Linking with these libraries will bloat your code a fair amount; if this is a concern you could modify BResourceSet only to natively handle archived bitmaps. If you'd like to be able to read bitmaps of any type, you can override GenerateBitmap() to go through the Translation Kit.
(One warning about using PNG images with QuickRes: the PNG Translator in R5 has a number of frustrating quirks when used with QuickRes, particularly in bitmaps with an alpha channel. The next release of BeOS will fix these problems.)
To show BResourceSet in action, I've included a modified
version of the old "LoadAddon" example
The interesting part here is in the add-on, where we use
BResourceSet to load a bitmap image for its button. To get
the BResourceSet up and running, we have:
Retrieving our bitmap from the add-on's resources is now
just a matter of
I hope this class gets you on your way to using resources
throughout your BeOS applications. Dig in to the class and
enhance it if you want to -- it's very easy to add your own
datatypes to the API -- or find other uses for it. I'm told
that it makes simple user interface skinning very easy
to implement...
This function simply returns the BResourceSet instance for
the add-on image, initializing it if that has not been
done yet. Notice how it passes in an address of your
application's image -- the gResourcesCreated variable,
which is in the image's data section. The use of atomic_or()
makes the function thread-safe (the BResourceSet object
itself is already thread-safe), and the class is instantiated
as a global so that it will be destroyed automatically when
the add-on is unloaded.
static int32 gResourcesCreated = 0;
static BResourceSet gResources;
BResourceSet& Resources()
{
// Return the BResourceSet object for this image,
initializing
// if needed.
if ((gResourcesCreated&2) != 0) {
// Quick return if already initialized.
return gResources;
} else if (atomic_or(&gResourcesCreated, 1) == 0) {
// Not yet initialized -- do so.
gResources.AddResources(&gResourcesCreated);
// Mark that we are done.
atomic_or(&gResourcesCreated, 2);
} else {
// Wait for resources to be initialized.
while ((gResourcesCreated&2) == 0) snooze(50000);
}
return gResources;
}
This can be used for whatever nefarious purpose we wish.
In this case, it's placed into a little BBitmapButton
convenience class that displays it.
const BBitmap* bm = Resources().FindBitmap(B_PNG_FORMAT, "BeOS Logo");
Dianne Kyra Hackborn
<hackbod@angryredplanet.com>
Last modified: June, 2005
This web page and all material contained herein is the fault
and Copyright ©1998 Dianne Hackborn, unless otherwise noted. All rights
reserved.