Event Programming
Volume Number: 8
Issue Number: 6
Column Tag: Getting Started
Event-Based Programming 
How a Mac program communicates with the user.
By Dave Mark, MacTutor Regular Contributing Author
So far, you’ve learned how to call Macintosh Toolbox routines in both C and
Pascal. You’ve also learned a bit about resource management, mastering the art of WIND
based window creation. You’re now ready to take the next step towards Macintosh
guru-dom.
Event-based Programming
Most the programs we’ve created together have one thing in common. Each
performs its main function, then sits there waiting for a mouse click using this piece of
code:
/* 1 */
while ( ! Button() )
;
This chunk of code represents the only mechanism the user has to communicate
with the program. In other words, the only way a user can talk to one of our programs is
to click the mouse to make the program disappear! This month’s program is going to
change all that.
One of the most important parts of the Macintosh Toolbox is the Event Manager.
The Event Manager tracks all user actions, translating these actions into a form that’s
perfect for your program. Each action is packaged into an event record and each event
record is placed on the end of the application’s event queue.
For example, when the user presses the mouse button, a mouseDown event
record is created. The record describes the mouseDown in detail, including such
information as the location, in screen coordinates, of the mouse when the click
occurred, and the time of the event, in ticks (60ths of a second) since system startup.
When the user releases the mouse button, a second event, called a mouseUp event is
queued.
If the user presses a key, a keyDown event is queued, providing all kinds of
information describing the key that was pressed. An autoKey event is queued when a key
is held down longer than a pre-specified autoKey threshold.
Though there are lots of different events, this month we’re going to focus on four
of them: mouseDown, mouseUp, keyDown, and autoKey. Next month we’ll look at some of
the others.
Working With Events
Events are the lifeline between your user and your program. They let your
program know what your user is up to. Programming with events requires a whole new
way of thinking. Up until this point, our programs have been sequential. Initialize the
Toolbox, load a WIND resource, show the window, draw in it, wait for a mouse click,
then exit.
Event programming follows a more iterative path. Check out the flowchart in
Figure 1. From now on, our programs will look like this. First, we’ll perform our
program’s initialization. This includes initializing the Toolbox, loading any needed
resources, perhaps even opening a window or two. Once initialized, your program will
enter the main event loop.
Figure 1. The main event loop flowchart.
The Main Event Loop
In the main event loop, your program uses a Toolbox function named
WaitNextEvent() to retrieve the next event from the event queue. Depending on the type
of event retrieved, your program will respond accordingly. A mouseDown might be
passed to a routine that handles mouse clicks, for example. A keyDown might be passed
to a text handling routine. At some point, some event will signal that the program
should exit. Typically, it will be a keyDown with the key sequence Q or a mouseDown
with the mouse on the Quit menu item. If If it’s not time to exit the program yet, your
program goes back to the top of the event loop and retrieves another event, starting the
process all over again.
WaitNextEvent() returns an event in the form of an EventRecord struct:
/* 2 */
struct EventRecord
{
short what;
long message;
long when;
Point where;
short modifiers;
};
The what field tells you what kind of event was returned. As I said before, this
month we’ll only look at mouseDown, mouseUp, keyDown, and autoKey events, though
there are lots more. Depending on the value of the what field, the message field contains
four bytes of descriptive information. when tells you when the event occurred, and
where tells you where the mouse was when the event occurred. Finally, the modifiers
field tells you the state of the control, option, command and shift modifier keys when
the event occurred.
EventMaster
This month’s program, EventMaster, displays four lines, one for each of the
events we’ve covered so far. As EventMaster processes an event, it highlights that line.
For example, Figure 1 shows EventMaster immediately after it processed a mouseDown
event.
Figure 2. EventMaster in action.
EventMaster requires a single resource of type WIND. Create a folder called
EventMaster in your Development folder. Next, open ResEdit and create a new resource
file named EventMaster.π.rsrc inside the EventMaster folder. Create a new WIND
resource according to the specs shown in Figure 3. Make sure the resource ID is set to
128 and the Close Box checkbox is checked. Select Set ‘WIND’ Characteristics from the
WIND menu and set the window title to EventMaster. Quit ResEdit, saving your changes.
Figure 3. The WIND resource specifications.
Running EventMaster
Launch THINK C and create a new project named EventMaster.π in the
EventMaster folder. Add MacTraps to the project. Select New from the File menu and
type this source code in the window that appears:
/* 3 */
#include
#define kBaseResID 128
#define kMoveToFront (WindowPtr)-1L
#define kSleep MAXLONG
#define kRowHeight 14
#define kFontSize 9
#define kMouseDown 1
#define kMouseUp 2
#define kKeyDown 3
#define kAutoKey 4
/*************/
/* Globals */
/*************/
Boolean gDone;
short gLastEvent = 0;
/***************/
/* Functions */
/***************/
void ToolBoxInit( void );
void WindowInit( void );
void EventLoop( void );
void DoEvent( EventRecord * eventPtr );
void HandleMouseDown( EventRecord * eventPtr );
void DrawContents( void );
void SelectEvent( short eventType );
void DrawFrame( short eventType );
/******************************** main *********/
void main( void )
{
ToolBoxInit();
WindowInit();
EventLoop();
}
/*********************************** ToolBoxInit */
void ToolBoxInit( void )
{
InitGraf( &thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( nil );
InitCursor();
}
/******************************** WindowInit *********/
void WindowInit( void )
{
WindowPtr window;
window = GetNewWindow( kBaseResID, nil, kMoveToFront );
if ( window == nil )
{
SysBeep( 10 ); /* Couldn't load the WIND resource!!! */
ExitToShell();
}
SetPort( window );
TextSize( kFontSize );
ShowWindow( window );
}
/******************************** EventLoop *********/
void EventLoop( void )
{
EventRecord event;
gDone = false;
while ( gDone == false )
{
if ( WaitNextEvent( everyEvent, & event, kSleep, nil ) )
DoEvent( &event );
}
}
/************************************* DoEvent *********/
void DoEvent( EventRecord * eventPtr )
{
switch ( eventPtr->what )
{
case mouseDown:
SelectEvent( kMouseDown );
HandleMouseDown( eventPtr );
break;
case mouseUp:
SelectEvent( kMouseUp );
break;
case keyDown:
SelectEvent( kKeyDown );
break;
case autoKey:
SelectEvent( kAutoKey );
break;
case updateEvt:
BeginUpdate( (WindowPtr) eventPtr->message );
DrawContents();
EndUpdate( (WindowPtr) eventPtr->message );
}
}
/******************************** HandleMouseDown *********/
void HandleMouseDown( EventRecord * eventPtr )
{
WindowPtr window;
short thePart;
thePart = FindWindow( eventPtr->where, &window );
if ( thePart == inGoAway )
gDone = true;
}
/******************************** DrawContents *********/
void DrawContents( void )
{
short i;
WindowPtr window;
window = FrontWindow();
for ( i=1; i<=3; i++ )