The majority of user interface developers think in terms of the evcnt-action paradigm. As a consequence of this, most user interface software is constructed in a bottom-up fashion.
That is, individual event handlers are not written in their entirety at one time in the way that conventional subroutines would be written. Instead, the code in event handlers are usually built up gradually until a co-ordinated group of event handlers is achieved. Typically, each event handler starts with a basic sequence of actions that will be executed in response to an event. And because an item may respond to an event in different ways, depending on the context in which it is used, conditional statements are added to determine the context in which the event has occurred and therefore which actions to execute.
In order to determine the context in which an event has occurred, an event handler must make use of information shared between user interface objects. Such information is avail
able for any event handler to use. The information may not necessarily be an explicit global variable; it may, for example, be the value contained in a user interface object or any other attribute of an object.
To illustrate the bottom-up approach lo user interface construction, we will consider a calculator application that is distributed with Visual Basic version 5. The user interface of the application is shown in Figure 3.1 and the code that controls the user interface objects is given in Figure 3.2. The intention is not lo criticize this particular application, but rather the technique used to construct the software. The application has been chosen because it is small and yet it possesses many of the characteristics of much larger applications.
1
. That is. the customer is given 30 days to pay the bill before a reminder bill is sent.2D C ONCEPTS
Figure 3.1
Copyright (C) 1994 Microsoft Corporation
You have a royalty-free right to use, modify, reproduce and distribute the Sample Application Files (and/or any modified version) in any way you find useful, provided that you agree that Microsoft has no warranty, obligations or liability for any Sample Application Files.
Option Explicit Dim Op I, Op2 Dim DecimalFlag As Integer Dim NumOps As Integer Dim Lastlnput Dim OpFlag Dim TempReadout
Click event procedure for C (cancel) key.
Reset the display and initializes variables.
Private Sub Cancel_Click() Readout = Fomnat(0, "0.") Opl = 0
Op2 = 0 Fonm_Load End Sub
' Click event procedure for CE (cancel entry) key.
Private Sub CancelEntry_Click() Readout = Fomnat(0, "0.")
' Previously input operand.
' Decimal point present yet?
' Number of operands.
' Indicate type of last keypress event.
' Indicate pending operation.
Figure 3.2
A BO T TO M -U P A P P R O A C H T O U SER IN TERFA CE C O N S T R U C T IO N 2 1
DecimalFlag = False Lastlnput = "CE"
End Sub
' Click event procedure for decimal point (.) key.
' If last keypress was an operator, initialize ' readout to "0." Otherwise, append a decimal ' point to the display.
' Set all variables to initial values.
Private Sub Form_LoadQ
'Append new number to the number in the display.
Private Sub Number_dick(lndexAs Integer)
Readout = Left(Readout, lnStr(Readout, Fomnat(0,".")) - I) + Number(lndex).Caption+
Figure 3.2 (Cont’d)
2 2 CONCEPTS
Fonmat(0,".") End If
If Lastlnput = "NEG" Then Readout = & Readout Lastlnput = "NUMS"
End Sub
’Click event procedure for operator keys (+, x, /, -) 'If the immediately preceeding keypress was part of a 'number, increments NumOps. If one operand is present, 'set Op I . If two are present, set Op I equal to the 'result of the operation on Op I and the current 'input string, and display the result.
If Operator(lndex).Caption = And Lastlnput <> "NEG" Then Readout = & Readout
Lastlnput = "NEG"
End If Case I Op I = Readout
If Operator(lndex).Caption = And Lastlnput <> "NUMS" And OpFlag
<> "=" Then
A B O T T O M -U P A P P R O A C H T O U SER IN T E R F A C E C O N S T R U C T IO N 23
’ Click event procedure for percent key (%).
’ Compute and display a percentage of the first operand.
Private Sub Percent_C lick()
The code is divided into eight parts. It starts with a list of global variable declarations and then there are seven event handlers. The event handlers are for the following events: when the cancel button (C) is clicked; when the cancel entry button (CE) is clicked; when the decimal point button is clicked; when the application is first started; when a number button (0-9) is clicked; when an operator button (+, x, / , - ) is clicked; and when the percent button is clicked.
Although some readers may not be familiar with the syntax of the programming language, it should be possible to understand what each line of code means if the following points are bom in mind:
• ‘Readout’ is the name of the display field where the numbers appear after the number buttons are clicked.
• The operator buttons (+, x, / , =) are grouped so that when a user clicks one o f them, the same event handler is executed. Each button in the group has an index number and so, for example, the following conditional statement will check whether the minus button has been clicked:
24 CONCEPTS
If Operator(lndex).Caption = then ...
• In a similar way, the 10 number buttons (0 to 9) are also grouped so that when any one of them is clicked, the same event handler is executed.
In the event handler for the number buttons, the following statement appears:
Left(Readout, lnStr(Readout, Format(0,".")) - I )
This simply returns the characters to the left of the decimal point in the Readout field.
The following observations can be made about the approach used to construct the application:
• There is no abstract view of the software Attempting to understand the overall behav
iour of the software is difficult bccausc there is no abstract view of it. Instead, wc are left attempting to understand the software line-by-line and gradually build up a picture of how it all fits together.
• Event handlers are co-ordinated through the use of global variables A global vari
able is known throughout the entire application and may be used by any event handler.
Using global variables in an event handler makes the event handler impossible to understand in isolation from the rest of the code. Having several distinct units of code accessing the same shared information can easily result in unwanted side-effects when the software is modified. The application makes extensive use of global variables. The global variables are used to pass messages between event handlers so that the user interface objects behave in a co-ordinated way. For instance, the number_click event handler makes use of the Lastlnput and DecimalFlag global variables. Thus, to under
stand the numbcr_click event handler, its is necessary to identify and understand the other parts of the application that use these global variables. In short, although the application code is divided into separate event handlers, they are actually bound together by global information.
• The software is not object oriented An important characteristic of an object oriented system is that no information is shared between objects. Instead, the slate of the system is distributed throughout the objects and each object is responsible for managing ils own state information. For instance, when a user interacts with a scrollbar, a message is sent to the scrollbar object to update the position of the sliding button. The position of ihe sliding button is a visual representation of the object's internal state. The event handler code in the calculator example is not object oriented because the event handlers share information in the form of global variables.
• The contexts are not explicit Many of the event handlers contain conditional staie- menls to determine the context in which an event occurs. A combination of the event and the context determines which lines of code are executed. Unfortunately, ihe contexts are not explicitly named in the code. Instead, conditions are tested in the code but it is not always clear why those conditions are being tested and why certain lines of code arc executed if those conditions arc true. In short, the different contexts in which an event can occur were not explicitly identified before the code was written.
• It is difficult to get the software to w ork correctly The bottom-up approach to user interface development relics on the skill of the developer to identify all the possible ways in which a user can supply events to the application. This is not an easy task and it must be approached in a systematic way. To illustrate the point, I tested the calcu
lator application for about an hour and found 10 errors, three of which caused the application to crash with runtime errors. (The full details of the errors are given in Appendix A.) Certainly, the application adds, subtracts, multiplies and divides
A BOTTOM-UP APPROACH TO USER INTERFACE CONSTRUCTION 25
numbers successfully, but it is the peripheral functions such as the cancel entry button, the percent button and operations on negative numbers that caused the errors.
For instance, the cancel entry (CE) button is used for cancelling the last operand entered and yet it can be used at any point during the execution of the application.
Ideally the button should be disabled when it is not valid to use it, such as before an operand has been entered. As the application is written currently, a user is forced to learn the syntax of the user interface if the button is to be used correctly.
The percent button is intended to divide the first operand by 1()0. However, the button can be clicked at any point. Again, the user is being forced lo learn the syntax of the user interface if errors in the application are to be avoided. Ideally, the button should only be enabled when the first operand is being entered, or when a result from an oper
ation is achieved.
The application is about 100 lines long and yet it contains a number of serious errors.
Business applications are much larger and more complicated than this and yet they are often constructed in a similar bottom-up way.
It is difficult to enhance such software Software constructed in a bottom-up way is difficult to make significant enhancements to. The relationships between user inter
face objects are usually very simple. And when user interface software is written it is obvious why event handlers chcck certain conditions and why they execute different actions depending on those conditions. However, many events can affect or make use of the same information and if the behaviour of a user interface needs to be changed, then it is necessary to understand the dependences that exist between objects. It can be difficult to understand the impact of modifying one part of the code without making the change and then testing the software to ensure no side-cffects have been intro
duced. Developing software by trial and error is not good.
Summary
The proliferation of user interface development tools has not been matched by a growth in effective ways of developing code with such tools. In general, user interface code is largely constructed in an ad hoc, informal way without an explicit design. A bottom-up approach to constructing user interface software is not a scalable approach - even simple applications can be difficult to get right.
Most people find it easier to understand a diagram than written text. A diagram is generally more concise and more memorable than words alone. When reading user interface code, trying to understand, visualize and remember the dependences between fragments of code can be difficult. To make user interface software easier lo understand, a design notation is required that provides a clear view of how the software works together as a whole. The next few chapters will describe a technique for designing user interface software in a top-down fashion. The calculator application will then be revisited and designed so that it contains significantly fewer errors and is easier to understand.