List of Controls
Volume Number: 5
Issue Number: 6
Column Tag: Advanced Mac'ing
Related Info: List Manager Control Manager
List of Controls LDEF
By James Plamondon, Berkeley, CA
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
List-Of-Controls LDEF
Introduction
I recently needed to implement a list, in which each cell would be an active
control. It turned out to be a little harder to do than I thought it would be. To save
others the trouble, I thought I’d describe how I made it all work.
Overview
I use Apple’s List Manager to manipulate my list of controls. Each element of the
list contains a ControlHandle to a PopUp-Menu control. Whenever a mouseDown event
occurs in the list, I find out if it is one of the controls, and if so, call TrackControl()
(IM v1 p323) to deal with it. If the mouseDown is in the list, but not in an active part
of the control, I pass the event off to LClick() (IM v4 p273), so the Apple List
Manager can handle it.
Whenever the user selects or unselects a list element, or brings a new control
into view by scrolling the list, the List Manager will send a message to the list’s LDEF
telling it to draw or hilite the list element. My LDEF will then set the control’s hilite
state as needed to assure that it is drawn properly, and then call Draw1Control() (IM
v4 p53) to do the actual drawing.
A routine called FixCtlRects() is called in the test program whenever a control
may have changed position in the list’s display, to make sure that its controlRect field
matches its list rectangle, and that it won’t be drawn if it is out of the list’s display
area.
The example program uses the PopUp-Menu control described in MacTutor,
September 1988, PopUp-Menu Control CDEF. I could have used a different CDEF, but I
would have had to include the CDEF’s source code, and I didn’t want to make this article
any longer than necessary.
LDEFs
LDEFs are described on pages 278-277 of Inside Mac, Volume Four, in the List
Manager chapter (pages 259-282). The ‘LDEF’ associated with a list is loaded into
memory when the list is created. Like other _DEFs (MDEFs, CDEFs, etc.), the code
resource has a single entry point at its first byte (i.e., it has no jump table). The
entry point must be a procedure with the following definition:
PROCEDURE MyLDEF(
lMessage: INTEGER;
lSelect: Boolean;
lRect: Rect;
lCell: Cell;
lDataOffset,
lDataLen: INTEGER;
lHandle: ListHandle);
The function and argument names may be changed, of course, but their order and
type must be as shown here (from IM v4 p276).
When a new list is created using the LNew() function (IM v4 p270), one of the
parameters you must give to LNew() is the resource ID of the LDEF you want the list to
use. Apple’s default LDEF has a resource ID of 0. Our LDEF has a resource ID of
RES_ID (128). When we create the new list, the List Manager will load the specified
LDEF, and store a handle to it in the listDefProc field of the resulting list record.
Whenever a cell of the list needs to be drawn or hilited, the List Manager will send a
message to the LDEF, and the LDEF will do the work.
LDEF Messages
An LDEF message is just a procedure call to the LDEF’s MyLDEF() routine, with
lMessage (an integer) equal to a meaningful value. If the List Manager wants the LDEF
to draw a cell, it calls MyLDEF() with lMessage equal to lDrawMsg (1). If the cell is
to be hilited, MyLDEF() is called with lMessage equal to lHiliteMsg (2). MyLDEF()
contains a CASE statement, keyed to the lMessage parameter’s value. If lMessage equals
lDrawMsg, the routine doDrawMsg() is called. If lMessage equals lHiliteMsg,
doHiliteMsg() is called. That’s all there is to LDEF message passing.
There are four different messages an LDEF may receive. Two of them, lDrawMsg
and lHiliteMsg, must be handled by all LDEFs.
When the LDEF gets a lDrawMsg message, it needs to draw the given list cell. If
the list cell is selected (ie., if lSelect is TRUE), then you need to hilite the cell, to tell
the user that it’s been selected. The LDEF used in this article simply calls
Draw1Control() to draw the control stored in the given cell. First it has to diddle with
the control’s rectangle, though, as we’ll see later.
The lHiliteMsg message is sent to the LDEF when a previously drawn cell needs to
be hilited or unhilited. In most cases, this is done by simply inverting the cell’s
rectangle. Our LDEF has to get a little fancier.
The other two LDEF messages, lInitMsg and lCloseMsg, need only be implemented
by the LDEF if each list handled by the LDEF needs to be initialized in some way, and
then have its initialization voided when the list is disposed of. In this LDEF, however,
no such initialization or deinitialization need be done, so it does nothing when it
receives those messages.
List Hilighting
We need to be able to draw the control in such a way as to make it look selected in
the list. The mechanism for inverting the control’s title already exists in the CDEF, so
we’ll use that to indicate that the control is selected in the list.
The careful readers of the PopUp-Menu CDEF article and code may have noticed
that a partCode was defined for the title of the pop-up menu (titlePart = 2), but that
this partCode was never returned by doTestCntl(). The part code exists solely for this
article. By setting the contrlHilite field of the control to be drawn to titlePart before
calling Draw1Control(), we can guarantee that the control will have its title hilighted
when it is drawn.
The Example Program
The example program is a very stripped Mac application. It simply initializes
all of the usual managers, throws up a dialog, and cycles through ModalDialog() (IM
v1 p415) until the user selects the dialog’s OK button. Most of the work of handling
the list and the controls it contains is done by the filterProc MyFilter() -- the
address of which is passed to ModalDialog() in the main program -- and its helper
functions, doMouseDown() and doKeyDown().
After the user clicks the OK button, the program exits the ModalDialog() loop,
disposes of the controls, list, and dialog, and quits. The final values of the controls are
not accessed, because I’m not actually doing anything with them. In a real program
you’d get the final values out of the controls, using GetCtlMax() and GetCtlMin() as
described in PopUp-Menu Control CDEF.
Using the List-Of-Controls LDEF
The LDEF expects to find a control handle in each of its cells. We have to put
these handles there ourselves. First we need to create the list itself, which we do with
a call to LNew() in the routine InitList(). The only interesting part of this call is the
use of RES_ID (128, the resource ID of our LDEF), rather than zero (0), Apple’s
default LDEF.
Next, we need to create the controls we wish to add to the list. This is
accomplished with calls to NewControl() (IM v1 p319), in the routine ReadData(). I
read the resource ID’s of the controls I need from the ‘INT#’’ resource I’ve placed in
the resource fork, and then read in the associated ‘CNTL’ resources.
I give the control a rectangle that is out of sight. It gets drawn despite my saying
that I want it to be invisible (very irritating), so I just hide it, so that it can’t be seen
until I want it to be seen.
After getting a control handle from NewControl(), the control is made visible
(but not drawn) by placing TRUE (255) in its contrlVis field. ShowControl() (IM v1
p322) would try to draw the control, but that’s a waste of time, because the window is
still invisible.
Finally, the control handle is placed in a new cell in the list. From there, we can
extract it, and pass it along to Draw1Control() and TrackControl() as necessary.
Problems
At first I thought that I could just update a control’s rectangle whenever the cell
it was in received an lDrawMsg or an lHiliteMsg. But that alone won’t work, because
the List Manager uses ScrollRect() (IM v1 p187) to scroll the bits inside the list’s
rectangle, and only redraws cells as they come into view. This means that after the
initial drawing of the list, the only place in which cells are redrawn is in the top cell
frame or in the bottom cell frame. If you modify the control rectangle to match the cell
rectangle only when the cell is redrawn, then sooner or later, all of the controls are
going to have either the top or bottom cell frame as their rectangles. It may look like
there are controls in the middle of the list, but those are just scrolled bit-images; the
controls will think they are in the top and bottom frames.
So, when you click in a control in the middle of the list, the control does not
respond because it’s not really there. The thing you thought was a control was just a
picture of a control. The control is in either the top or bottom cell frame (depending
on where it was drawn last). If you click in the top or bottom cell frame, you’ll click
in a control, all right, but not necessarily the one you see there. Your click will get
picked up by the first control in the window’s control list (not the List Manager list)
that was recently redrawn in that frame. Argh!
Here comes the kludge. The only way the user can move the controls on the list is
with the list’s scroll bar. Therefore, whenever we receive a mouseDown event in the
list that is not in one of our controls, we have to call FixCtlRects(). FixCtlRects()
just walks through the list, getting each cell’s rectangle and setting the rectangle of the
control in the cell to match. If the cell is invisible, then control is made invisible, and
vice versa. This doesn’t take too long for a short list, but if you had a couple of
hundred controls, it would be noticeably slow.
We also have to call FixCtlRects() after reading in the controls the first time, to
make sure they’re displayed properly.
It’s a kludge, but it works, so what the hey.
Miscellaneous Notes
There are few other things about the LDEF, the pop-up menu CDEF, and its
example program that are worth mentioning.
Note that I pass POINTER(-1) to TrackControl() in doMouseDown(). This
enables the pop-up menu’s CDEF’s autoTrack function. This is the function that pops
up the PopUp-Menu via PopUpMenuSelect() (IM v5 p241), as described in detail in
PopUp-Menu Control CDEF.
CenterWindow() just centers a window in the screen, about two-thirds of the
way up.
Acknowledgements
I started this project in order to do something we needed to do at work, but it kind
of took on a life of its own when I realized that there was an article in it. I’d like to
thank the guys at Abacus -- Dan, Jim, and Will -- for letting me spend the time I did
on this. It won’t happen again for another week or two, anyway.
Listing: Test.make
# Test.make
#
#
Test ÇTest.r ControlLDEF.LDEF PopMenuCDEF.CDEF
Rez -rd Test.r -o Test
Test ÇTest.p.o ∂
ControlLDEF.LDEF
PopMenuCDEF.CDEF ∂
Test.r
Link ∂
Test.p.o ∂
“{Libraries}”Runtime.o ∂
“{Libraries}”Interface.o ∂
“{PLibraries}”PasLib.o ∂
-o Test
ControlLDEF.LDEF ControlLDEF.p.o
Link -sg ControlLDEF ∂
-rt LDEF=1 ∂
-m MYLDEF ControlLDEF.p.o ∂
“{Libraries}”Interface.o ∂
-o ControlLDEF.LDEF
PopMenuCDEF.CDEF PopMenuCDEF.p.o
Link -sg PopMenuCDEF ∂
-rt CDEF=1 ∂
-m MYCONTROL PopMenuCDEF.p.o ∂
“{Libraries}”Interface.o ∂
“{PLibraries}”PasLib.o ∂
-o PopMenuCDEF.CDEF
PopMenuCDEF.p.o àPopMenuCDEF.p PopMenuIntf.p
Pascal PopMenuCDEF.p -o PopMenuCDEF.p.o
Test.p.o àTest.p
Pascal Test.p -o Test.p.o
ControlLDEF.p.o àControlLDEF.p PopMenuIntf.p
Pascal ControlLDEF.p -o ControlLDEF.p.o
# END OF FILE: Test.make