THE PROGRAMMER'S GUIDE. BASIC 7.1
CHAPTER 9: EVENT HANDLING

This chapter explains how to detect and respond to events that occur while your program is running. This process is called "event handling". After reading this chapter, you will know how to:

O.~ Specify an event to trap and enable event handling.
O.~ Write a routine to process the trapped event.
O.~ Return control from an event-handling routine.
O.~ Write a program that traps any keystroke or combination of keystrokes.
O.~ Trap music events and user-defined events.
O.~ Trap events in programs composed of more than one module.

EVENT TRAPPING VS. POLLING

Many times during program execution, an event occurs which requires the program to suspend normal operation and take some action. The event could be the operator pressing the Ctrl+Break key combination, the arrival of data at the communications port, or the ending of a phrase of music playing in the background of a computer game.

There are two ways to detect these events: polling and event trapping. To understand the difference, imagine we are in a loop which will continue forever unless the operator presses Ctrl+C. To detect this with polling, you write code that must be executed repeatedly. In this example, the Inkey$ function is performed every time the loop is executed:

Do
'Normal flow of program occurs here.
...
'Loop until operator presses Ctrl+C (Ascii 03).
Loop Until Inkey$ = Chr$(3)
'Program interrupted by the operator stops here.
End

Although polling works for the preceding example, it can degrade performance to check for events this way. And if the loop is too big, you might miss events that occur too quickly. A better alternative for many cases is to use event trapping.

This scheme allows Basic to do the detection on an interrupt basis and redirect the program as soon as the interrupt is detected. For the preceding scenario, the event trap would look like the following (the details of this example are described in the next section):

'Define the key depression to look for (Ctrl+C),
'where to go when it's pressed, and turn on event trapping.
Key 15, Chr$(4) + Chr$(46)
On Key (15) Gosub Handler
Key (15) On
Do
'Normal program flow occurs here.
...
Loop
'Branch here when Ctrl+C is pressed.
Handler:
End

HOW TO TRAP EVENTS

There are three steps necessary for event trapping:

1. Set the trap by telling the program where to branch to when a specific event occurs.
In the preceding example, this is accomplished by the On Key (15) Gosub statement which directs the program to the label Handler.

2. Turn on event trapping for the particular event you want.
In the preceding example, the Key (15) On does this.

3. Write a routine that takes action based on the specific event that has occurred, the event-handling routine.

In the preceding example, the action was very simple: the program is terminated with the End statement. At other times, you may need to go back to the main program. In that case, you put a Return statement at the end of the event-handling routine.

If you are trapping a predefined event (such as the pressing of one of the function keys), only the three preceding steps are required. Otherwise, you need another line of code to define the event that is to be trapped.

This was done in the preceding example with the Key statement which informed Basic that the key we were looking for was Ctrl+C. Examples of trapping predefined and user-defined events can be found throughout this chapter.

WHERE TO PUT THE EVENT-HANDLING ROUTINE

The event-handling routine must be in the module-level code. This is the only place where Basic will look for it. If you accidentally put it in a Basic procedure, you will get a Label not found error at run time.

Usually the event-handling routine goes after the End statement as in this sample fragment:

End
SampleHandler:
Print "You have pressed the F1 Key."
Print "It caused a branch to the event-handling routine."
Return

The event-handling routine is located here so that it does not get executed during normal program flow. You could also put it above the End statement and skip around it with the Goto statement, but putting the event-handling routine after End is the better programming practice.

TRAPPING PREASSIGNED KEYSTROKES

To detect any of the following preassigned keystrokes and route program control to a key-press routine, you need both of the following statements in your program:

On Key(n%) Gosub line
Key(n%) On

The following two lines cause the program to branch to the KeySub routine each time the F2 function key is pressed:

On Key(2) Gosub KeySub
Key(2) On

The following four lines cause the program to branch to the DownKey routine when the Down direction key is pressed and to the UpKey routine when the Up Arrow key is pressed:

On Key(11) Gosub UpKey
On Key(14) Gosub DownKey
Key(11) On
Key(14) On
...

Important.~ For compatibility with previous versions of Basic, the On Key (n) Gosub statement traps all function key depressions whether or not they are shifted. This means that you cannot use event trapping to distinguish between, for example, F1, Ctrl+F1, Alt+F1 and Shift+F1.

TRAPPING USER-DEFINED KEYSTROKES

In addition to providing the preassigned key numbers 1-14 (plus 30 and 31 with the 101-key keyboard), Basic allows you to assign the numbers 15 - 25 to any of the remaining keys on the keyboard. The key can be any single key such as the lowercase "s" or it can be a key combination, such as Ctrl+Z, as explained in the next two sections.

DEFINING AND TRAPPING A NON-SHIFTED KEY

To define and trap a single key, use these three statements:

Key n%, Chr$(0) + Chr$(code%)
On Key(n%) Gosub line
Key(n%) On

Here, n% is a value from 15 to 25, and code% is the scan code for that key. (See Appendix A in the Basic Language Reference for a listing of keyboard scan codes). The CHR(0) function, used in the first line, tells Basic that the trapped key is a single key.

The following example causes the program to branch to the Tkey routine each time the user presses the lowercase "t":

'Define key 15 (the scan code for "t" is decimal 20):
Key 15, Chr$(0) + Chr$(20)

'Define the trap (where to go when "t" is pressed):
On Key(15) Gosub Tkey
Key(15) On 'Turn on detection of key 15.

Print "Press q to end."
Do 'Idle loop: wait for user to
Loop Until Inkey$ = "q" 'press "q", then exit.
End

Tkey: 'Key-handling routine
Print "Pressed t.": Return

DEFINING AND TRAPPING A SHIFTED KEY

This is how to trap the following key combinations:

Key n%, Chr$(keyboardflag%) + Chr$(code%)
On Key(n%) Gosub line: Key(n%) On

Here, n% is a value from 15 to 25, code% is the scan code for the primary key, and keyboardflag% is the sum of the individual codes for the special keys pressed.

For example, the following statements turn on trapping of Ctrl+S. Note these statements are designed to trap the Ctrl+S (lowercase) and Ctrl+Shift+S (uppercase) key combinations. To trap the uppercase S, your program must recognize capital letters produced by holding down the Shift key, as well as those produced when the Caps Lock key is active, as shown here:

'31 = scan code for S key
'4 = code for Ctrl key
Key 15, Chr$(4) + Chr$(31) 'Trap Ctrl+S.

'5 = code for Ctrl key + code for Shift key
Key 16, Chr$(5) + Chr$(31) 'Trap Ctrl+Shift+S.

'68 = code for Ctrl key + code for Capslock
Key 17, Chr$(68) + Chr$(31) 'Trap Ctrl+Capslock+S.

On Key (15) Gosub CtrlSTrap 'Tell program where to
On Key (16) Gosub CtrlSTrap 'branch (note: same
On Key (17) Gosub CtrlSTrap 'routine for each key).

Key (15) On 'Activate key detection for
Key (16) On 'all three combinations.
Key (17) On
...

The following statements turn on trapping of Ctrl+Alt+Del:

'12 = 4 + 8 = (code for Ctrl key) + (code for Alt key)
'83 = scan code for Del key
Key 20, Chr$(12) + Chr$(83)
On Key(20) Gosub KeyHandler
Key(20) On
...

Note in the preceding example that the Basic event trap overrides the normal effect of Ctrl+Alt+Del (system reset). Using this trap in your program is a handy way to prevent the user from accidentally rebooting while a program is running.

If you use a 101-key keyboard, you can trap any of the keys on the dedicated keypad by assigning the string as to any of the n% values from 15 to 25:

Chr$(128) + Chr$(code%)

Example.~ The following example shows how to trap the Left Arrow keys on the dedicated cursor keypad and the numeric keypad.

'128 = keyboard flag for keys on the dedicated cursor keypad
'75 = scan code for Left Arrow key

Key 15, Chr$(128) + Chr$(75) 'Trap Left key on
On Key(15) Gosub CursorPad 'the dedicated
Key(15) On 'cursor keypad.

On Key(12) Gosub NumericPad 'Trap Left key on
Key(12) On 'the numeric keypad.

Do: Loop Until Inkey$ = "q" 'Start idle loop.
End

CursorPad: Print "Pressed Left key on cursor keypad."
Return

NumericPad: Print "Pressed Left key on numeric keypad."
Return

TRAPPING MUSIC EVENTS

When you use the Play statement to play music, you can choose whether the music plays in the foreground or in the background. If you choose foreground music (which is the default) nothing else can happen until the music finishes playing. However, if you use the MB (Music Background) option in a Play music string, the tune plays in the background while subsequent statements in your program continue executing.

The Play statement plays music in the background by feeding up to 32 notes at a time into a buffer, then playing the notes in the buffer while the program does other things. A "music trap" works by checking the number of notes currently left to be played in the buffer. As soon as this number drops below the limit you set in the trap, the program branches to the first line of the specified routine.

To set a music trap in your program, you need the following statements:

On Play(queuelimit%) Gosub line
Play On
Play "MB"
Play commandstring$
...

Here, queuelimit% is a number between 1 and 32. For example, this fragment causes the program to branch to the MusicTrap routine whenever the number of notes remaining to be played in the music buffer goes from eight to seven:

On Play(8) Gosub MusicTrap
Play On
...
Play "MB" 'Play subsequent notes in the background.
Play "o1 A# B# C-"
...
MusicTrap:
'Routine to play addition notes in the background.
...
Return

Important.~ A music trap is triggered only when the number of notes goes from queuelimit% to queuelimit -\~1 . For example, if the music buffer in the preceding example never contained more than seven notes, the trap would never occur. In the example, the trap happens only when the number of notes drops from eight to seven.

Example.~ You can use a music-trap routine to play the same piece of music repeatedly while your program executes, as shown in the following example:

'Turn on trapping of background music events:
Play On

'Branch to the Refresh subroutine when there are fewer than
'two notes in the background music buffer:
On Play(2) Gosub Refresh

Print "Press any key to start, q to end."
Pause$ = Input$(1)

'Select the background music option for Play:
Play "MB"

'Start playing the music, so notes will be put in the
'background music buffer:
Gosub Refresh
I = 0

Do
'Print the numbers from 0 to 10,000 over and over until
'the user presses the "q" key. While this is happening,
'the music will repeat in the background:
Print I: I = (I + 1) Mod 10001
Loop Until Inkey$ = "q"
End

Refresh:
'Plays the opening motive of
'Beethoven's Fifth Symphony:
Listen$ = "t180 o2 p2 p8 L8 GGG L2 E-"
Fate$ = "p24 p8 L8 FFF L2 D"
Play Listen$ + Fate$
Return

TRAPPING A USER-DEFINED EVENT

This section uses assembly language examples and calls to the Dos operating system. You may want to skip it if you are unfamiliar with these items.

Trapping a user-defined event involves writing a non-Basic routine, such as in Microsoft Macro Assembler (Masm) or C, to define the event and inform Basic when the event occurs. Once this is done, and the routine is installed, program flow continues much as in the preceding examples but uses these statements instead:

On Uevent Gosub line
Uevent On

Example

As an example of trapping a user-defined event, suppose you have a special task that needs to be done every 4.5 seconds. The following code accomplishes this, using the system timer chip which provides an interrupt to the CPU 18.2 times per second.

The interrupt is Dos number 1CH. The address where Dos expects to find the far pointer to the interrupt service routine is 0:70H. The code requires three Masm routines. The first one, SetInt, informs Dos that a new interrupt service routine ErrorHandler is to be executed whenever interrupt 1CH occurs.

The second routine, EventHandler, is called 18.2 times per second. It increments a counter. After 82 interrupts (4.5 seconds) it calls the SetUevent routine which is loaded from the Basic main library during compilation.

The routine sets a flag indicating that a user event has occurred. When Basic encounters the flag, it causes the Basic program to branch to the SpecialTask routine indicated in the On Uevent Gosub statement. The third routine, RestInt, restores the original service routine for the interrupt when the Basic program terminates.

.model medium, basic ;Stay compatible
.data ;with Basic.
.code
SetIntprocuses ds ;Get old interrupt vector
mov ax, 351CH ;and save it.
int 21h
mov word ptr cs:OldVector, bx
mov word ptr cs:OldVector + 2, es

push cs ;Set the new
pop ds ;interrupt vector
lea dx, Eventhandler ;to the address
mov ax, 251CH ;of our service
int 21H ;routine.
ret
SetIntendp

public EventHandler ;

Make the following routine public

EventHandler proc ;for debugging.
extrn SetUevent: proc ;Define Basic library routine.
push bx
lea bx, cs:TimerTicks ;See if 4.5 secs have passed.
inc byte ptr cs:[bx]
cmp byte ptr cs:[bx], 82
jnz Continue
mov byte ptr cs:[bx], 0 ;if true, reset counter,
pushax ;save registers, and
pushcx ;have Basic
pushdx ;set the user
pushes ;event flag.
callSetUevent
pop es
pop dx ;Restore registers.
pop cx
pop ax
Continue:
pop bx
jmp cs:OldVector ;Continue on with the
;old service routine.

TimerTicks db 0 ;Keep data in code segment
OldVector dd 0 ;where it can be found no
;matter where in memory the
EventHandler endp ;interrupt occurs.

RestIntprocuses ds ;Restore the old
lds dx, cs:OldVector ;interrupt vector
movx, 251CH ;so things will
int 21h ;keep working when
ret ;this Basic program is
RestIntendp ;finished.
End

The Basic program shown here provides an outline of how our special task is performed using the Uevent statement. The program first installs the interrupt, sets the path to the Basic event-handling routine and then enables event trapping.

'Declare external Masm procedures.
Declare Sub SetInt
Declare Sub RestInt
'Install new interrupt service routine.
Call SetInt

'Set up the Basic event handler.
On Uevent Gosub SpecialTask
Uevent On

Do
'Normal program operation occurs here.
'Program ends when any key is pressed.
Loop Until Inkey$ <> ""

'Restore old interrupt service routine before quitting.
Call RestInt

End

'Program branches here every 4.5 seconds.
SpecialTask:
'Code for performing the special task goes here, for example:
Print "Arrived here after 4.5 seconds."
Return

GENERATING SMALLER, FASTER CODE

Event trapping adds execution time and code length to a Basic program. To make your programs smaller and faster, you can turn off event trapping in sections where it is unnecessary. Do this with the Event Off statement, as shown in the following example.

When Event Off is encountered by the compiler, it stops generating code to trap events, but the events will still be detected. When the compiler encounters the Event On statement, code is inserted to re-enable the traps. Any previously detected event, and any newly occurring event, will cause the program to branch to the appropriate event-handling routine.

On Key (1) Goto Handler1
On Key (2) Goto Handler2
Key (1) On
Key (2) On
'All events are trapped here.
...
Event Off
'Events are still detected but no longer trapped.
'Code generated for these statements is smaller and faster.
...
Event On
'Events are trapped again including previously detected ones.
...
End

Handler1: 'F1 key event-handling routine goes here.
Return
Handler2: 'F2 key event-handling routine goes here.
Return

TURNING OFF AND SUSPENDING SPECIFIC EVENT TRAPS

If you want to selectively turn off certain event traps and leave others on, use the event Off statement. The event occurring after an event Off statement has been executed is then ignored. This is shown by the following example where the F1 key trap is turned off, but the trap for F2 is left on:

On Key (1) Goto Handler1
On Key (2) Goto Handler2
Key (1) On
Key (2) On
'Both key traps are turned on here.
...
Key (1) Off
'The F1 trap is turned off and ignored here, but we still trap
F2.
...

Key (1) On
'Now we can trap them both again.
...
End
Handler1: 'F1 key event-handling routine goes here.
Return

Handler2: 'F2 key event-handling routine goes here.
Return

Sometimes you need to suspend event trapping, without turning it off. This allows you to record events that occur and take action on them after a specific time period has elapsed.

To suspend event trapping, use the event Stop statement as demonstrated in the next example. In the following example, after the event Stop statement is encountered, if the timer event occurs, there is no branch to the event-handling routine.

However, the program remembers that the event occurred, and as soon as trapping is turned back on with event On, it immediately branches to the ShowTime routine.

'Once every minute (60 seconds), branch to the ShowTime routine:
On Timer(60) Gosub ShowTime

'Activate trapping of the 60-second event:
Timer On
...
Timer Stop 'Suspend trapping.
'A sequence of lines you don't want interrupted,
'even if 60 or more seconds elapse.
...
Timer On 'Reactivate trapping.
'If a timer event occurs above, it will now
'be handled by the ShowTime routine.
...
End

ShowTime:
'Get the current row and column position of the cursor,
'and store them in the variables Row and Column:
Row = Csrlin
Column = Pos(0)

'Go to the 24th row, 20th column, and print the time:
Locate 24, 20
Print Time$

'Restore the cursor to its former position
'and return to the main program:
Locate Row, Column
Return

EVENTS OCCURRING WITHIN EVENT-HANDLING ROUTINES

If an event occurs during an event-handling routine, and the event trap is on for the new event, then the program branches to the event-handling routine for this new event. For instance, in the first example in the preceding section.

If the F2 key is pressed while Handler1 is executing, the program branches to Handler2. When Handler2 is finished, the program returns to Handler1 and then goes back to the main program. The only time that branching doesn't occur in a event-handling routine is if both events are the same.

In this case branching cannot occur because event-handling routines execute an implicit event Stop statement for a given event whenever program control is in the routine. This is followed by an implicit event On for that event when program control returns from the routine.

For example, if a key-handling routine is processing a keystroke, trapping the same key is suspended until the previous keystroke is completely processed by the routine. If the user presses the same key during this time, this new keystroke is remembered and trapped after control returns from the key-handling routine.

EVENT TRAPPING ACROSS MODULES

Events whose traps are turned on in one module are detected and trapped in any module that is running. This is demonstrated in the following program where a trap set for the F1 function key in the main module is triggered even when program control is in the other module.

'=========================================================
'Module
'=========================================================
On Key (1) Gosub GotF1key
Key (1) On
Print "In main module. Press c to continue."

Do: Loop Until Inkey$ = "c": Call Subkey
Print "Back in main module. Press q to end."
Do: Loop Until Inkey$ = "q"
End

GotF1key:
Print "Handled F1 keystroke in main module."
Return

'=========================================================
'Subkey Module
'=========================================================
Sub Subkey Static
Print "In module with Subkey. Press r to return."

'Pressing F1 here still invokes the GotF1key
'subroutine in the Main module:
Do: Loop Until Inkey$ = "r"
End Sub

Output:
In main module. Press c to continue.
Handled F1 keystroke in main module.
In module with Subkey. Press r to return.
Handled F1 keystroke in main module.
Back in main module. Press q to end.
Handled F1 keystroke in main module.

COMPILING PROGRAMS FROM THE COMMAND LINE

When compiling code containing any of these statements from the command line you must use one of the compiler options described in Table 8.1.

On Event Gosub
Event On

The /V option detects events sooner, however the program will run slower and take up more memory. As an alternative, you can add labels to your program at the places where you need detection and compile with the /W option as shown in this example:

On Key (1) Gosub F1Handler
Key (1) On
...
Checkpoint1: 'Check for an event here.
'Continue processing without checking.
...
Do Until Condition% 'Check for event every time we loop.
Checkpoint2:
...
Loop 'No more event checking.
...
End
F1Handler: 'Code to take action when F1 is pressed goes here.
Return