Jun 99 Getting Started
Volume Number: 15
Issue Number: 6
Column Tag: Getting Started
Resource Templates
by Dan Parks Sydow
How a Mac programmer defines custom resources to
hold program data
In last month's Getting Started article we looked at how a Mac program works with
resources stored in resource files. This month we carry on with our look at resources.
Rather than work with resources of the standard types such as WIND, MENU, and
DLOG, though, we're now ready to work with custom resources of our own design.
By now you're very familiar with how a program works with standard resources. With
a little extra effort your program can also work with resources that you define. While
the predefined resource types suffice for use with program interface elements such as
windows, menus, and dialog boxes, they won't due if your program wants to store
application-specific data in a resource. Last month's Getting Started article
demonstrated how a program can easily make use of resources stored in an external
resource file. One good use of an external resource file is for the storing of application
data - particularly program preferences.
Resource Templates
Graphical resource editors like Apple's ResEdit and Mathemaesthetics' Resorcerer
depend on templates to make resource editing easy. A template displays the value, or
values, that make up a single resource of a single resource type. For example, you use
the MBAR template to edit the strings in an MBAR resource. When working with a
resource editor such as ResEdit, this use of templates is invisible. You double-click on
a resource, and ResEdit uses the template associated with that resource type as the
means for formatting and displaying the data held in the resource. Figure 1 shows a
menu bar resource being edited with the use of the MBAR template. The template,
which is built into ResEdit, displays the IDs of the menus that the menu bar resource
defines to be a part of one menu bar.
Figure 1. An MBAR resource as viewed with the MBAR template.
You don't have to use the built-in MBAR template when viewing an MBAR resource. If
you click on an MBAR resource and then choose Open Using Hex Editor from the
Resource menu, you'd see the resource values in a window like the one shown in
Figure 2. After looking at this window, though, you'll see that the use of a template
makes viewing a resource's data much clearer.
Figure 2. Viewing an MBAR resource without the use of the MBAR template.
ResEdit supplies templates for dozens of the commonly used resource types, including
the ALRT, DITL, and DLOG, MBAR, and MENU resources. For most of your programming
needs, these built-in templates are all that you'll ever need. If you want to store your
own data structures in a resource file, however, you'll want to create your own
template so that you can view and edit your data in a simple fashion. You'll also want to
create your own data-holding resources - the resources that ResEdit will format with
the new template. Fortunately, ResEdit makes it easy to add your own template and
custom resources to a resource file. On the following pages we'll create both a template
and a custom resource. And to save a little work, the template and resource we create
will be the same ones we'll use in this month's example program.
Creating a Resource Template
Using ResEdit, a new resource of any type is created by choosing Create New Resource
from the Resource menu. In the dialog that appears you see the standard resource types
named in a list. A template is itself a resource - a resource of the type TMPL.
Double-click on TMPL in the list and ResEdit creates a new, empty template - as
shown in Figure 3.
Figure 3. A new, empty TMPL resource.
The template resource itself won't be used by your application. Instead, it serves as
nothing more than a tool to tell ResEdit how to display data of a particular type in
ResEdit. That means you need to specify which resource type the new template is to
affect. Do this by clicking on the template's ID in the TMPL window of the resource
file, and then choosing Get Resource Info from the Resource menu. In the dialog box
that opens, type in the name of the resource type that the template should format. At
this time you'll need to think of a name for the data-holding resources you'll soon be
creating. That is, you're creating a format-defining template resource now, and you'll
be creating the actual data-holding custom resource (or resources) later. In Figure
4 you see that for our example the new template is named TSTD, for "TeST Data." When
you choose to define a new resource type, keep in mind that the name should be four
characters, but that Apple reserves type names that appear in all lowercase. So make
sure to include at least one uppercase character in your template's name.
Figure 4. Giving the new template a name.
After closing the Info window, it's time to edit the template. The template will have an
item for each data element that will be in a (yet-to-be-created) TSTD resource. Of
course if you choose to give your template a different name, then your template won't
be used to format TSTD resources. Rather, it will be used to format resources of the
type you specify.
A template item provides a label for the data element and specifies the type of data that
the element is. Imagine that our TSTD resource will hold just a single data element,
and that this data element will be a two-byte number. In C, such a number could be
represented by a short variable. In the template, there should be a single item that
corresponds to this one data element. Figure 5 shows what this item would look like.
Figure 5. A template resource that defines one data field.
A template item can have any label, but the label should be somewhat descriptive of
what the data element is or what it will be used for. The label write was selected
because in the program code this value will be used as a flag to determine whether the
program should write to a window. The item has to have a type, and it must be one of
the types that ResEdit recognizes. ResEdit doesn't recognize C, or Pascal, or any other
standard programming language. Instead, it has it's own simple set of data types. For a
two-byte value, ResEdit defines the DWRD type. DWRD stands for decimal word. The
most commonly used ResEdit types are:
DWRD
2-byte word decimal field (maximum value of 32,767)
DLNG
4-byte long word decimal field (maximum value of 2,147,483,647)
PSTR
Pascal string field (though no leading "\p" is required)
CHAR
1-byte character field
RECT
Rectangle field, displaying four coordinate holding edit boxes (T, L, B, R)
A template can of course hold more than one item - but for this introduction we'll stop
at one item so that we can run a test that proves the template works. Then we'll go back
and add a couple of more items to this same template.
Creating a Custom Resource
To create a custom resource - one of a type not defined by ResEdit - choose Create New
Resource from the Resource menu. You're creating a resource of your own resource
type, so don't look for the type in the list in the Select New Type dialog box. Instead,
type in the edit box the four characters that make up the custom type. This resource
type should match the template type you defined earlier. For our example, that would
be TSTD. Click the OK button and a new TSTD resource appears. As shown in Figure 6,
the resource will be formatted using the TSTD template (compare the TSTD resource
in Figure 6 with the TSTD template resource shown back in Figure 5).
Figure 6. A custom TSTD resource displayed using the TSTD template.
Now that we're satisfied that we can create data-holding resources that will be easily
viewable and editable with our own template, it's time to go back to the template
resource and add a couple of more items to it. Open the resource file's one TMPL
resource, click on the area where a second item is to be added (the "2) *****
string), and choose Insert New Field(s) from the Resource menu. Type in a label and a
type. Repeat for each item that is to appear in the template. As shown in Figure 7, for
our TSTD template we'll have three fields: a 2-byte number, a 4-byte number, and a
string.
Figure 7. The TSTD template with three items.
Our example program will eventually use the data that's held in one TSTD resource.
The program will look at the value of the write item to see if the other TSTD resource
data should be written to a window. A write value of 0 means no, a write value of 1
means yes. The score item will hold a high score (we'll assume we're writing a game),
and the name item will hold the name of the person who holds the high score. The
details of how the program accesses this data become evident just ahead.
Next, open the one existing TSTD resource. Because the template this resource uses has
been altered (we've added two items), you'll see an alert that warns you that items are
being added to the TSTD resource. After dismissing the alert you'll see that the TSTD
resource now has edit boxes for entering values for the three items. In Figure 8 you
see values entered in each item.
Figure 8. A TSTD resource with three items.
Now let's see how our TSTD resource would be viewed in ResEdit if there wasn't a
corresponding TSTD template. Figure 9 shows the TSTD resource pictured in Figure
8 - but here it's opened using ResEdit's hex editor. The same data is present, it's just
not formatted in a manner that's easy to view and easy to edit.
Figure 9. A TSTD resource viewed without the aid of a template.
For any given resource type, you only need to create one template resource. Now that
our TMPL resource of type TSTD is completed, any newly created TSTD resource will
use it. To verify that, choose Create New Resource from the Resource menu, type in
TSTD as the resource type, and click the OK button. Figure 10 shows that ResEdit
creates a new empty resource and displays it with the help of the TSTD template.
Figure 10. A new TSTD resource automatically uses the TSTD template.
Using Custom Resource Data in an Application
When a Mac application makes a call to GetNewWindow(), information from a WIND
resource is loaded into a WindowRecord data structure. The program is then provided
with a pointer to this structure:
WindowPtr wind;
wind = GetNewWindow( 128, nil, (WindowPtr)-1L );
Your program can use the WindowPtr variable to obtain information stored in the
WindowRecord. That's possible because the WindowRecord data type is defined in the
universal header files and is known to the Toolbox.
When you want an application that you're writing to access information from a
resource type that you've defined, you'll have to supply the application with the format
in which the data is stored. Just as a program needs to recognize a WindowRecord data
structure before it can work with the data from a WIND resource, your application
needs to have a data structure defined for any programmer-defined resource type.
Consider our own TSTD resource. Any resource of that type contains values that
correspond to a C language short, long, and Str255 - in that order. That means an
application that is to use a TSTD resource needs to define a data structure that matches
this format. Here's that structure:
typedef struct
{
short write;
long score;
Str255 name;
} TemplateRecord, *TemplatePtr, **TemplateHandle;
While we've given each field a name that corresponds exactly to the label of a TSTD
resource item, that naming scheme isn't a requirement. As long as the data structure
consists of fields that are of data types that match the resource item data types, the
structure is valid.
Now, when the application needs to access information from a TSTD resource, it makes
a call to the Toolbox routine Get1Resource() to load the resource data into memory and
to return a handle to the data.
Handle dataHandle;
dataHandle = Get1Resource( 'TSTD', 128 );
Get1Resource() can be used to load into memory one resource of any type. The first
parameter is the resource type, and the second is the ID of the particular resource to
load.
Once the TSTD resource data is loaded into memory, it can be accessed by the
application - but not until the application is told the format of the data. Until then, the
data appears as just a stream of information in memory. Typecasting the generic
dataHandle variable to a TemplateHandle is the way to tell the application how the data
is formatted. To gain access to one piece of data, the TemplateHandle is dereferenced
twice. Here's how a short variable would be assigned the value of the first item in the
TSTD resource.
Handle dataHandle;
short resValue;
dataHandle = Get1Resource( 'TSTD', 128 );
resValue = (**(TemplateHandle)dataHandle).write;
After the above code executes, the resValue variable can be used, and the first member
of the TSTD data in memory can be ignored. This applies to each of the three TSTD data
members: once variables are assigned the values held in the resource, you don't have to
use the handle to the resource data. The following snippet loads the TSTD data into
memory, then uses assignment statements to extract all of the data from that resource.
After the snippet executes, the variables resValue1, resValue2, and resValue3 hold the
data that is in the resource.
Handle dataHandle;
short resValue1;
long resValue2;
Str255 resValue3;
StringPtr sourceStr;
Size bytes;