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