Jul 99 Getting Started
Volume Number: 15
Issue Number: 7
Column Tag: Getting Started
Preferences Files
By Dan Parks Sydow
How a Mac program sets and retrieves program
preferences
Two months back -- in May's Getting Started article -- we looked at how a program
reads resources stored in an external resource file. In last month's Getting Started
article we discussed how a Mac program reads data from a resource of a
programmer-defined type. This month we introduce two new topics: creating a new
resource file "on-the-fly" (during application execution), and writing data to a
resource of a programmer-defined type. When we combine the concepts we covered in
the previous two Getting Started articles with this month's new topics, the result is all
the code necessary to provide your own application with the ability to create, read,
modify, and make use of a preferences file.
Custom Resource Recap
Last month we developed the RsrcTemplate program to demonstrate how a Mac
application works with resources of a custom type. Recall that we used ResEdit to
create a single template resource of the standard resource type TMPL. In that resource
we defined a number of fields, with each field having a ResEdit data type that
corresponded to a C/C++ data type. Figure 1 shows last month's one TMPL resource,
which we named TSTD. Recall that the name we chose was somewhat arbitrary (TSTD
could be thought of as standing for TeST Data) -- the template name doesn't in any way
define or limit the eventual contents of the template resource.
Figure 1. The TSTD template with three items.
A template resource itself doesn't hold data. Instead, a single TMPL resource serves to
format any number of resources of still another type. That other type of resource is a
custom type that bears the same name as the template that formats it. Continuing on
with last month's example, Figure 2 shows that a custom TSTD resource displays its
data in an easily readable manner because the TMPL resource of the same name is
automatically used by ResEdit to display the resource.
Figure 2. A custom TSTD resource displayed using the TSTD template.
Figures 1 and 2 use the TSTD TMPL resource and the TSTD resource type -- but
you'll note that in the figures these resources are held in a file named PrefFile.rsrc.
This month's program PrefFile uses the exact same TMPL and TSTD resources that
were developed last month. Yes, we did in fact have a valid reason for spending time
creating those resources!
Once a custom resource exists, its data can be used by an application. PrefFile, as last
month's RsrcTemplate program did, uses the value of the write item to see if the other
TSTD resource data should be written to a program window. A write value of 0 means
no, a write value of 1 means yes. The score item holds a high score (again, we're
assuming we're writing a game), and the name item holds the name of the person who
holds the high score.
An application accesses custom resource data by loading the resource into an
application-defined data structure. This data structure defines fields that match the
order and type of the data held in the resource. For the TSTD resource we came up with
a corresponding data structure that looked like this:
typedef struct
{
short write;
long score;
Str255 name;
} TemplateRecord, *TemplatePtr, **TemplateHandle;
When the application needs to access information from a TSTD resource, it calls the
Toolbox function Get1Resource() to load the resource data into memory and to return a
handle to the data.
Handle dataHandle;
dataHandle = Get1Resource( 'TSTD', 128 );
Accessing data is now done by typecasting the generic Handle to a handle of our own data
structure type. Once that's done, any field in the structure can be accessed. Here the
value of the write field is assigned to a local variable named resValue:
short resValue;
resValue = (**(TemplateHandle)dataHandle).write;
Opening the Preferences File
Last month's RsrcTemplate program read data from a custom resource. If an
application is to save user-defined data on disk for use in subsequent executions of the
program, then that application also needs to be able to write data to a custom resource.
Before writing a resource, though, we need to make sure the resource fork to be
written to is open. If the fork is the appLication resource fork, we're all set -- that
fork is always open. If, however, the fork is a part of an external file (which is the
case for a preferences file), we need to open that fork.
Two months back, in the May Getting Started article, we looked at how a program opens the resource fork of a file in order to gain access to that file's resources. May's
ResFiles example program used its OpenResourceFork() function to accomplish the
task.
void OpenResourceFork( void )
{
short volRef = 0;
long dirID = 0;
FSSpec rsrcFSSpec;
OSErr err;
FSMakeFSSpec( volRef, DirID, kRsrcFileName, &rsrcFSSpec );
gFileRsrcForkRef = FSpOpenResFile( &rsrcFSSpec, fsRdPerm );
err = ResError();
if ( err != noErr )
DoError( "\pOpening resource file failed" );
UseResFile( gFileRsrcForkRef );
}
The above version of OpenResourceFork() works fine for a file that we know will be in
the same folder as the application -- but that's not always this case. For a preferences
file, that's almost never the case. Instead, a program should keep its preferences file
in the Preferences folder in the System Folder on the user's startup drive. So instead
of setting both the volume reference number and directory ID to 0 (which indicates
that the item in question is in the same folder as the application), we'll use the Toolbox
function FindFolder() to obtain a path to the user's Preferences folder.
FindFolder() is used to obtain path information to the Preferences folder, Apple Menu
Item folder, Control Panels folder, and other system directories. The following is a
typical call to the function.
short volRef;
long dirID;
FindFolder( kOnSystemDisk, kPreferencesFolderType,
kDontCreateFolder, &volRef, &dirID );
The first argument specifies the reference number of the volume that holds the folder
in question. System-related folders should of course be on the startup disk, so the
Apple-defined constant kOnSystemDisk is used here. To determine which folder to
search for, FindFolder() accepts an Apple-defined constant as its second argument. The
constant kPreferencesFolderType works for us. The third argument, which can be still
another Apple-defined constant, specifies whether or not a new folder of this name
should be created if an existing one can't be found. We'll assume that the user certainly
has a Preferences folder. In return for this information, FindFolder() fills the fourth
and fifth parameters with the volume reference number and directory ID of the
sought-after folder.
With the path to the preferences file established, a file system specification, or
FSSpec, for the file can be created. That FSSpec is then used in a call to
FSpOpenResFile() to open the preferences file's resource fork. Since your program
may be writing information to the file, make sure to pass the Apple-defined constant
fsRdWrPerm as the second argument. This opens the file with both read and write