Object Toolbox
Volume Number: 7
Issue Number: 11
Column Tag: C Workshop
Related Info: List Manager Window Manager Control Manager
Objects and the Toolbox
By Rob Hafernik, Austin, TX
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
Think C Objects and the Toolbox
[Rob Hafernik is lucky enough to be paid to program the Macintosh, by
Technology Works, in Austin, Texas.]
All Mac programmers should be using MacApp these days. No question about it,
it’s where the action is in Mac programming. Once in a while, however, you come upon
a project that MacApp isn’t suited for. It could be that MacApp’s application sizes are
too large for some tight project or that the project has to be in C, or whatever. It must
be a good reason, or you wouldn’t want to part with all the goodies MacApp gives you for
free.
This is the position I found myself in recently, and I started thinking about ways
to combine Object Programming with the Mac Toolbox, but without MacApp. This is a
bit difficult, since none of the Toolbox routines know an object from a linked list. I did,
however, come up with a couple of good ideas, and they’re what this article is about.
This particular article explains a method for combining objects with the Toolbox
List Manager, but the method could be used equally well with the Window Manager or
Control Manager. Anywhere that the Toolbox allows for definition procedures, you can
sneak an object in. This will give you all of the great aspects of Object Programming
while still letting the Mac Toolbox do lots of the work.
Let’s back up a little. The List Manager is great if you want to display a list or
grid of text, but to display anything but text (such as pictures or icons), you have to
write your own definition procedure for a custom list. You also have to write your own
defproc if you want items in a list to be highlighted in some way other than the standard
(with a check mark, for example). Writing a definition procedure is a nasty chore that
most of us shy away from since a defproc is a code resource and code resources are a
pain to write and a pain to debug. If you’re in the dark about defprocs for the List
Manager, now would be a good time to refer to Inside Macintosh, vol IV, page 276. There
is a nice explanation of the definition procedure for lists and how they work.
When you think about the List Manager, it’s only natural to think: “A list of
what”? Well, a list of objects would certainly be nice. If each item in a list were an
object, then each object in the list could also have methods to do things like draw itself
or perform some action when it’s double-clicked. While we’re at it, let’s make the list
itself an object with methods to manipulate the list and display it.
That’s just what we have here: A list object that holds information about the list
and has methods to add items, display them, select them and so on. The List object also
encapsulates the List Manager so that the programmer doesn’t need to deal with it or
ever write another List Manager definition procedure. Each item in the list is also an
object that is a sub-class of an Item object that we’ll define. The sub-class can do
anything it needs to for a particular application.
The example program I’ll use to demonstrate these objects is very simple,
almost to the point of being useless. The application, which I call Icon List, can open a
file’s resource fork and display any icons it finds there in a list in a window. If you
look at the inheritance diagram in figure 1, you’ll see the classes we’ll use in this
example. I’ll talk about the CListObj object first.
Figure 1
A CListObj contains a handle to a regular Toolbox List Manager data structure, a
rectangle for viewing the list in a window, and a flag to turn drawing on and off (for
efficiency when adding a lot of items to a list - it’s a lot faster to turn off drawing, add
them all in, then turn drawing back on).
The List Manager in the Mac Toolbox defines a data structure that includes a
handle to the code that will actually draw the items in the list and highlight them. If
you’re displaying a regular, text-based list (such as the one Standard File displays),
then you’ll use the built-in code that is provided by the List Manager (‘LDEF’ resource
id 0). If you’re customizing the list to display something else, you provide a block of
code in a code resource (of type ‘LDEF’) and tell the List Manager to use that code.
Of course that means that the code you write has to be in a code resource and
that’s a lot of trouble. There is a nice little trick that avoids all of this, however. I
wish I could take credit for inventing this trick, but I can’t. I don’t even know who did
it first, but it was at least three or four years ago. Here’s how the trick works: You
have an LDEF resource, true, but it contains only a JMP instruction to some code in
your regular application. Before you initialize a list manager list, you open this “fake
defproc” resource and copy into it a JMP instruction and the address of the routine in
your application code that will function as a list defproc (make sure that your code is in
a locked segment!). The list manager will take the handle, dereference it and jump to
it. Your JMP instruction will then jump to your code and off you’ll go. When your code
returns, the return address of the List Manager is right there on the stack. You now
have code in your application that the List Manager thinks is a regular, old defproc -
that you can write, compile, link and debug just like any other code.
The Methods
Now for the methods of CListObj. The first is IList. This is always called first to
initialize the list. It sets up the List Manager list, initializes it’s fields and sets up the
handle for the “Fake” defproc. The Dispose method does just the reverse, throwing
everything away. Notice that it does not dispose of the items in the list - the
application code should do that, usually before disposing of the CListObj. The next few
methods take care of the usual events you have in a Mac program: mouse clicks, update
events and activate events. Actually, you can see that most of the work is done by the
List Manager, with the CListObj just acting as glue code.
Then there are methods for setting flags (DrawingOn) and scrolling the list
(Scroll) and counting the number of items in the list (NumObjs). These are pretty
simple, as the code shows (the Toolbox does all the work, and that’s the way we want
it). Next comes the Add method. Notice that Add takes a CItemObj as it’s argument. If
you look at the definition of CItemObj, you’ll see that it’s nearly empty. It consists of
just one method, DrawItem. In CItemObj, even DrawItem doesn’t do anything but go
into the debugger with message. This is because each program that uses a CListObj
must sub-class CItemObj to do whatever is specific to that program. In the example at
hand, the sub-class is called CIconItemObj, and it holds information about an ‘icon’
resource. In addition, the CIconItemObjs’ DrawItem method overrides
CItemObj->DrawItem to actually plot the icon and show it’s name and number. The Add
method of a CListObj knows nothing of this, of course. It just gets the CIconItemObj and
adds it to the list (notice that it just adds a reference to it, it’s handle, to the list). In
like fashion, the Remove method removes a CItemObj reference from the list (without
disposing the actual object).
Now for the fun part. If you’ve used MacApp, you know about that wonderful
object, TList. A TList is like our CListObj here, but it does not display it’s list, it just
manages it. And manage it does; there are dozens of useful methods. So useful, in fact,
that I’ve stolen several of them for the CListObj object. The first is ForEach. ForEach
takes a function pointer and calls the function once for each object in the list, passing
each object to the function. It’s the equivalent of a for loop getting each item and calling
a function with each one. You can use this all sorts of ways. If the items in the list
were e-mail recipients, for example, the e-mail application would do something like:
recipientList->ForEach ( SendMessage );
Now, isn’t that a nice, readable bit of code? The next method, ForEachSelected,
is the same except it only calls the function for items in the list that are currently
selected.
After ForEachSelected come a couple of methods to manipulate and report on the
current selection, SelectNone and AnySelected. Then comes FirstThat, which is similar
to ForEach in that it walks down the list calling a function you supply and passing each
item in the list. FirstThat, however, stops as soon as the function you’ve supplied
returns true for one of the items. In other words, FirstThat does a search for an item
in the list, using the criteria in the function you pass and returning the matching item.
The IconList application shows a CListObj in action (well, some of it). When the
users selects the “Open” menu pick, they get a standard file dialog. Once they select a
file, the program opens a window and starts up a CListObj. Then it opens the file’s
resource fork and looks for ‘icon’ resources. For each ‘icon’ resource it finds, the
program allocates a CIconItemObj, fills it’s fields (we could have defined an IIconItem
method to do initialization for us) and adds it to a list. It also sets the windowKind field
of the window to something special and puts a reference to the CListObj in the window’s
refCon field where the application can find it later. Although the Icon List application
builds the list once and for all, another application could keep adding and removing
items from the list all the time. Once the window is up and the list is built, the event
loop takes care of it.
For example, if an update event comes along, the program checks to see if the
window is an “Icon List” window (using it’s windowKind field). If it is, the program
pulls the CListObj from the window’s refCon field and calls the UpdateList method of the
CListObj. What happens next is a little complicated, so keep an eye on Figure 2. The
event loop calls the UpdateList method. The UpdateList method calls the Toolbox List
Manager with the handle to the list and tells it to update. The Toolbox List Manager calls
our “fake” defproc to draw each item in the update region. The “fake” defproc calls a
dispatching function, called ListFunction, each time. The ListFunction only knows that
the list is made up of a sub-class of the CListObj. It pulls the object in question out of
the list and calls it’s DrawItem function with a highlight flag set true or false. After
that, it’s up to the DrawItem method to do whatever it wants to in it’s rectangle. Notice
that we get all the selection logic and auto-scroll for free from the Mac Toolbox.
Activate and MouseDown events are similar.
That’s all there is to it. You include the CListObj code in your application,
sub-class CItemObj to do whatever you want and you’ve got a displayed list with a
bunch of extras. Object Programming is really nice for programmers. Suppose we
wanted to add support for color icons. All we would have to do is sub-class CItemObj
again with a CColorIconItemObj and change FillIconList to also scan for ‘cicn’ resources
and we would be done (note that CListObj and the event loop would not change at all).
CListObj could also be sub-classed. If an application allowed users to double-click on a
list item to do something to that item, CListObj could be sub-classed and support for
double-clicks (timing and all) added with a “DoubleClick” method. That method could
detect a double-click, get the CItemObj from the list and call a “DoubleClicked” method
that’s been added to it. You wouldn’t rewrite CListObj, just add to it.
Figure 2.
A similar technique can be used for other definition functions. A Control
Manager defproc, for example, would be an object with fields to hold it’s data and
methods to respond to the Control Managers calls for drawing, highlighting, setting the
value and so on. It would also use a “fake” defproc handle and a dispatching function to
trick the Toolbox into using objects.
I always like to hear reactions to my work. I can be reached via e-mail at
America On Line as ‘rob042’ or you can catch me as ‘shokwave@well.sf.ca.us’ on the
internet.
/*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
CItemObj.h - Defines an abstract List Item
class that must be overriden.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- */
#ifndef CLASS_COBJECT
#include "CObject.h
#endif
#define CLASS_CITEMOBJ
/* :::::::::: item object :::::::::: */
struct CItemObj:CObject {
/*** no fields... */
/*** methods */
void DrawItem ( Boolean selected,
/* true if this item is to be
drawn selected */
Rect *dispR );