QuickRes: So You Have a Bunch of Resources -- Now What Do You Do?
by Dianne Hackborn, Software Engineer / Framework
This article was wrote for the Be newsletter, BMessage, which was published on September 6, 2000. Special thanks to Dianne Hackborn to allow us to add this article into our knowledge base. |
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:
BBitmap* myBitmap = BTranslationUtils::GetBitmap('type', "name"); BBitmap* myBitmap = BTranslationUtils::GetBitmap('type', id); |
While they are very convenient, there are some issues and limitations related to these functions that you should be aware of:
- They use the Translation Kit to create the bitmap. Currently, this means that all translator add-ons will be loaded into your application the first time you call GetBitmap(). For a small program this can impose a noticeable startup penalty.
- Every call to GetBitmap() generates a new bitmap. Not only does this include the overhead for creating the BBitmap object itself, but also for running the raw resource data through a translator.
- You can only use these to retrieve your application's resources. Add-ons cannot (easily) retrieve bitmaps from their own resources.
Another Way
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.
The API
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.)
An Example
To show BResourceSet in action, I've included a modified version of the old "LoadAddon" example. To build it, compile both of the projects (the application first, then the add-on in the Effects directory) or in the Terminal use the command "make -f makefile.all".
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:
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 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.
Retrieving our bitmap from the add-on's resources is now just a matter of
const BBitmap* bm = Resources().FindBitmap(B_PNG_FORMAT, "BeOS Logo"); |
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.
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 article and all material contained herein is the fault and Copyright ©1998 Dianne Hackborn, unless otherwise noted.
All rights reserved.