Events and Event Handlers in OS X
The number of different types of events that can occur is vast, but your program won't need to watch for, or respond to, every type of event. Instead, you'll define the event types for which your program should watch. You'll relay this information, along with information about an application-defined routine that handles the event, to the Carbon Event Manager. The routine is one that you write for the purpose of handling a particular event. It specifies what your program should do in response to the occurrence of an event of interest. Such a routine is called an event handler. After these steps are completed, you don't have to write any additional event-handling code because now the responsibility of watching for and handling particular events falls on the Carbon Event Manager.
Event Types
To distinguish one event from another, each Carbon event has an event class and an event kind associated with it. The event class specifies the broad category, such as a mouse event, to which the event belongs. The event kind further hones in on the particular nature of the event by specifying the kind of event, such as a mouse-down event, within the class. Together, the event class and event kind are referred to as an event type.
A commonly occurring event is the mouse-down event. Such an event is generated in response to a user clicking the mouse button. A click of the mouse button results in an event that has an event class of mouse event and an event kind of mouse-down. That wording might be understandable to you, but of course, the operating system needs that information in a more "code-like" manner. Apple defines a wealth of event class and event kind constants for this purpose. For a mouse click, the pertinent constants are as follows:
KEventClassMouse = FOUR_CHAR_CODE('mous'); KEventMouseDown = 1;
Both the event class constant and the event kind constant are integers, although the event class constant is specified by a four-character value that gets translated to a 32-bit integer (by way of the FOUR_CHAR_CODE macro).
In all cases, you'll be pleased to know that you aren't responsible for knowing any of the actual four-character or integer values. You need only become familiar with some of the constants with which you'll be working. The CarbonEvents.h header file lists them all. Tables 3.1 and 3.2 introduce you to two important event classes: the mouse event class (kEventClassMouse) and the window event class (kEventClassWindow). These tables list a few (but not all) of the event kinds in those two classes.
3.1 The Mouse Event Class (kEventClassMouse) and Some of Its Event Kinds
Event Kind |
Cause of Event Kind |
kEventMouseDown Mouse |
Mouse button clicked |
kEventMouseUp |
Mouse button released |
kEventMouseMoved |
Mouse position changed |
kEventMouseDragged |
Mouse dragged |
3.2 The Window Event Class (kEventClassWindow) and Some of Its Event Kinds
Event Kind |
Cause of Event Kind |
kEventWindowActivated |
Window brought to front |
kEventWindowDeactivated |
Window sent behind |
kEventWindowDrawContent |
Draw (update) window's contents |
kEventWindowShown |
Window became visible |
kEventWindowHidden |
Window became invisible |
kEventWindowCollapsed |
Window collapsed (minimized to Dock) |
kEventWindowExpanded |
Window expanded (enlarged from Dock) |
kEventWindowZoomed |
Window zoomed |
When an event class and an event type are paired, the result specifies one and only one type of event. Such a pairing is called an event type specifier. The EventTypeSpec is a structure that represents an event type specifier:
struct EventTypeSpec { UInt32 eventClass; UInt32 eventKind; };
Each event type has an event class and an event kind. Some event types also have event parameters (also called event attributes). The number, and purpose, of an event's parameters depends on the event type in question. For instance, the already-discussed mouse-down event type, which has an event class of kEventClassMouse and an event kind of kEventMouseDown, has four parameters that hold further information about the mouse-down event. Table 3.3 lists these event parameters and their purposes.
3.3 The Mouse-Down Event Parameters
Parameter |
Represents |
kEventParamMouseLocation |
Location of cursor at time of mouse click |
kEventParamKeyModifiers |
Modifier keys pressed at time of mouse click |
kEventParamMouseButton |
Mouse button clicked (for the instance of a multiple-button mouse) |
kEventParamClickCount |
Number of mouse clicks (such as a double-click) |
The EventTypeSpec keeps track of just two pieces of informationthe event class and event kindto define an event type. A different data type, the EventRef, keeps track of this same information and event parameter information. As shown in the struct definition, the composition of an EventTypeSpec structure is documented. The same isn't true of the EventRef data type.
The EventRef is an opaque type. Apple would prefer that a programmer not know its exact makeup. Making a data type opaque assures Apple that they can alter the internals of the data type at a future date without being concerned that programmers have written code that directly accesses fields of the data type.
When working with events, you'll declare a variable of type EventTypeSpec and then fill in the two fields of that structure. You'll also make use of an EventRef variable, but you'll often enable the system to fill in that structure's fields.
Your first step in handling an event is defining the event type in which your program is interested. To do this, declare an event type specifier for the event type of interest. For the upcoming discussion of installing an event handler, consider a program that's waiting for a click a button in a window. The class of such an event is considered a command, and the event kind is the processing of that command. For this situation, the event type specifier might look like this:
EventTypeSpec eventType; eventType.eventClass = kEventClassCommand; eventType.eventKind = kEventProcessCommand;
An alternate way of declaring the event specifier and assigning values to its fields would be to do the work at the time of the declaration:
EventTypeSpec eventType = { kEventClassCommand, kEventProcessCommand };
Installing an Event Handler
One of the primary jobs of the Carbon Event Manager system software is to watch for events. When this system software encounters an event about which your program is to be notified, it passes that event on to your program. In particular, it sends the event to a routine that you've written specifically to handle this type of event. Such a routine is an event handler. You'll need to write this routine (that task is covered next), and you'll need to install this routine.
To install the event handler means to provide the Carbon Event Manager system software with an association between the event type specifier (the type of event to be handled) and the event handler (the application-defined routine that should be executed at the occurrence of an event of the specified type). Pairing an event type with an event handler routine and making the Carbon Event Manager aware of this pairing requires a call to the Carbon API routine InstallEventHandler. Here's the prototype for that function:
OSStatus InstallEventHandler( EventTargetRef target, EventHandlerUPP handlerProc, UInt32 numTypes, const EventTypeSpec* typeList, void* userData, EventHandlerRef* handlerRef );
The InstallEventHandler parameter list might look a little daunting, so I'll cover this routine in detail here.
A program specifies an event type to watch for, and it defines an event handler routine to handle an occurrence of such an event. When the Carbon Event Manager encounters an event of the specified type and invokes the appropriate event handler routine, it needs to know upon what object the handler should act. In other words, you need to specify the target of the event. A target is a window, menu, control, or even the application itself. The target you specify should be the object "closest" to the event. For instance, if a program is watching for events that occur in a window, the window can be considered the target. Simply telling the Carbon Event Manager that a window is to be the target is not enough, though. You need to tell the Carbon Event Manager which window is the target. In addition, you need to supply this information as an EventTargetRef variable rather than as the window reference itself.
This target information is sent to the Carbon Event Manager by way of the first InstallEventHandler parameter, which is the target. You can create an EventTargetRef value by passing the intended target to the proper target routines: GetWindowEventTarget, GetMenuEventTarget, GetControlEventTarget, or GetApplicationEventTarget. Each routine takes the one argument passed to it and generates an EventTargetRef from that argument. In the following code snippet, the target of an event handler routine is to be a window that's referenced by the WindowRef variable window, and it's assumed that this window has already been created by a call to CreateWindowFromNib. The following code would generate an EventTargetRef that then could be used as the first argument to InstallEventHandler:
WindowRef window; EventTargetRef target; target = GetWindowEventTarget( window );
The second InstallEventHandler parameter is handlerProc, an EventHandlerUPP. The UPP in EventHandlerUPP stands for universal procedure pointer. In short, any time you see UPP in a variable or type name, you can expect that a pointer to a routine is involved. If the Carbon Event Manager is to invoke the event handler function, it needs to know where that function's code is in memory. The handlerProc variable holds a pointer to the event handler routine. You can generate such a pointer by calling the NewEventHandlerUPP function. Simply pass the name of the event handler routine to NewEventHandlerUPP and the function returns a pointer to that function. Assuming that the as-yet unwritten event handler routine will be called MyEventHandler, the call to NewEventHandlerUPP appears as shown in the following code. The resulting EventHandlerUPP then could be passed as the second argument to InstallEventHandler.
EventHandlerUPP handlerUPP; handlerUPP = NewEventHandlerUPP( MyEventHandler );
The third InstallEventHandler parameter, which is numTypes, is the number of event types to which this one event handler can respond. Although so far the focus has been on one event type and one event handler routine, it is possible to have a single event handler that's capable of handling more than one type of event.
The next parameter is typeLista pointer to the event type or event types that this event handler routine handles. As mentioned, an event handler can serve more than one event type. Here's where the Carbon Event Manager gets the EventTypeSpec for each event type. The typeList variable is a pointer that points to either one EventTypeSpec or to an array of EventTypeSpecs.
The userData parameter is used to pass a pointer to any information that might be of use to the event handler. The pointer is passed to the Carbon Event Manager, which in turn passes the pointer to the event handler routine each time it's called. One common use for this pointer is to use it to pass a pointer to the window in which the event took place. For the handling of some event types, it might not make sense to pass supplemental information, and in such a case, you can use a value of NULL here.
The last InstallEventHandler parameter is a pointer to an event handler reference. This is a value that the Carbon Event Manager fills in for use by your program. Your program will need to use this value only if your program will be dynamically changing the event types that make use of the event handler routine. This is a situation you won't encounter often, so expect to simply pass a value of NULL here.
Now let's gather things together and take a look at a snippet that defines one type of event and installs an event handler that's to handle events of that type. The event type is used to watch for a command (such as a mouse click on a button in a window), and the event handler routine that will handle such an event is named MyEventHandler.
WindowRef window; EventTypeSpec eventType; EventTargetRef target; EventHandlerUPP handlerUPP; eventType.eventClass = kEventClassCommand; eventType.eventKind = kEventProcessCommand; target = GetWindowEventTarget( window ); handlerUPP = NewEventHandlerUPP( MyEventHandler ); InstallEventHandler( target, handlerUPP ), 1, &eventType, (void *)window, NULL );