Functions as first-class values play a central role in the design of graphical user interfaces. The term ``interface'' refers to the boundary between the program and a user. As long as we are the only users, we can apply functions to data in DrScheme's Interactions window. If we want others to use our programs, though, we must provide a way to interact with the program that does not require any programming knowledge. The interaction between a program and a casual user is the USER INTERFACE.
A GRAPHICAL USER INTERFACE (GUI) is the most convenient interface for casual users. A GUI is a window that contains GUI items. Some of these items permit users to enter text; others are included so that users can apply a specific function; and yet others exist to display a function's results. Examples include buttons, which the user can click with the mouse and which trigger a function application; choice menus, from which the user can choose one of a collection of values; text fields, into which the user can type arbitrary text; and message fields, into which a program can draw text.
Take a look at the simple GUI in figure
. The left-most
picture shows its initial state. In that state, the GUI contains a text
field labeled ``Name'' and a message field labeled ``Number'' plus a
``LookUp'' button. In the second picture, the user has entered the name
``Sean'' but hasn't yet clicked the ``LookUp'' button.
Finally, the right-most picture
shows how the GUI displays the phone number of ``Sean'' after the user
clicks the ``LookUp'' button.
The core of the program is a function that looks up a phone number for a
name in a list. We wrote several versions of this function in
part
but always used it with DrScheme's
Interactions
window. Using the GUI of figure
,
people who know nothing about Scheme can now use our function, too.
To build a graphical user interface, we build structures
that correspond to the
GUI items and hand them over to a GUI manager. The latter constructs the
visible window from these items. Some of the structures' fields describe
the visual properties of the GUI's elements, such as the label of a button,
the initial content of a message field, or the available choices on a
menu. Other fields stand for functions. They are called CALL-BACK FUNCTIONS
because the GUI manager calls--or applies--these functions
when the user manipulates the corresponding GUI element. Upon application,
a call-back function obtains strings and (natural) numbers from the
elements of the GUI and then applies the function proper. This last step
computes answers, which the call-back function can place into GUI elements
just like graphics functions draw shapes on a canvas.
The ideal program consists of two completely separate components: the
MODEL,
which is the kind of program we are learning to design,
and a VIEW,
which is the GUI program that manages the display of
information and the user's mouse and keyboard manipulations. The bridge
between the two is the CONTROL
expression. Figure
graphically illustrates the organization, known as the
MODEL-VIEW-CONTROL
architecture. The lowest arrow indicates how a
program makes up a button along with a call-back function. The
left-to-right arrow depicts the mouse-click event and how it triggers an
application of the call-back function. It, in turn, uses other GUI
functions to obtain user input before it applies a core function or
to display results of the core function.
The separation of the program into two parts means that the definitions for the model contain no references to the view, and that the definitions for the view contain no references to the data or the functionality of the model. The organization principle evolved over two decades from many good and bad experiences. It has the advantage that, with an adaptation of just the bridge expression, we can use one and the same program with different GUIs and vice versa. Furthermore, the construction of views requires different tools than does the construction of models. Constructing views is a labor-intensive effort and involves graphical design, but fortunately, it is often possible to generate large portions automatically. The construction of models, in contrast, will always demand a serious program design effort.
Here we study the simplified GUI world of the teachpack
gui.ss. Figure
specifies the operations that the
teachpack provides.
The GUI
manager is represented by the function create-window. Its contract
and purpose statement are instructive. They explain that we create a window
from a list. The function arranges these lists in a corresponding number of
rows on the visible window. Each row is specified as a list of
gui-items. The data definition for gui-items in
figure
shows that there are four kinds:
How all this works is best illustrated with examples. Our first example is a canonical GUI program:
(create-window (list (list (make-button ``Close'' hide-window))))It creates a window with a single button and equips it with the simplest of all call-backs: hide-window, the function that hides the window. When the user clicks the button labeled ``Close'', the window disappears.
The second sample GUI copies what the user enters into a text field to a message field. We first create a text field and a message field:
(define a-text-field (make-text ``Enter Text:''))Now we can refer to these fields in a call-back function:(define a-message (make-message ```Hello World' is a silly program.''))
;; echo-message : X -> true ;; to extract the contents of a-text-field and to draw it into a-message (define (echo-message e) (draw-message a-message (text-contents a-text-field)))The definition of the call-back function is based on our (domain) knowledge about the gui-items. Specifically, the function echo-message obtains the current contents of the text field with text-contents as a string, and it draws this string into the message field with the draw-message function. To put everything together, we create a window with two rows:
(create-window
(list (list a-text-field a-message)
(list (make-button ``Copy Now'' echo-message))))
The first row contains the text and the message field; the second one
contains a button with the label ``Copy Now'' whose call-back
function is echo-message. The user can now enter text into the
text field, click the button, and see the text appear in the message
field of the window.
The purpose of the third and last example is to create a window with a choice menu, a message field, and a button. Clicking the button puts the current choice into the message field. As before, we start by defining the input and output gui-items:
(define THE-CHOICES (list ``green'' ``red'' ``yellow''))Because the list of choices is used more than once in the program, it is specified in a separate variable definition.(define a-choice (make-choice THE-CHOICES))
(define a-message (make-message (first THE-CHOICES)))
As before, the call-back function for the button interacts with a-choice and a-message:
;; echo-choice : X -> true
;; to determine the current choice of a-choice and
;; to draw the corresponding string into a-message
(define (echo-choice e)
(draw-message a-message
(list-ref THE-CHOICES
(choice-index a-choice))))
Specifically, the call-back function determines the 0-based index
of the user's current choice with choice-index, uses Scheme's
list-ref function to extract the corresponding string from
THE-CHOICES, and then draws the result into the message field of
the window. To create the window, we arrange a-choice and
a-message in one row and the button in a row below:
(create-window
(list (list a-choice a-message)
(list (make-button ``Confirm Choice'' echo-choice))))
;; Model: ;; build-number : (listof digit) -> number ;; to translate a list of digits into a number ;; example: (build-number (list 1 2 3)) = 123 (define (build-number x) ...);; ;
;; View: ;; the ten digits as strings (define DIGITS (build-list 10 number->string))
;; a list of three digit choice menus (define digit-choosers (local ((define (builder i) (make-choice DIGITS))) (build-list 3 builder)))
;; a message field for saying hello and displaying the number (define a-msg (make-message ``Welcome''))
;; ;
;; Controller: ;; check-call-back : X -> true ;; to get the current choices of digits, convert them to a number, ;; and to draw this number as a string into the message field (define (check-call-back b) (draw-message a-msg (number->string (build-number (map choice-index digit-choosers)))))
(create-window (list (append digit-choosers (list a-msg)) (list (make-button ``Check Guess'' check-call-back))))
Now that we have examined some basic GUI programs, we can study a program
with full-fledged core and GUI components. Take a look at the definitions
in figure
. The program's purpose is to echo the values
of several digit choice menus as a number into some message field. The
model consists of the build-number function, which converts a list
of (three) digits into a number. We have developed several such functions,
so the figure mentions only what it does. The GUI component of the program
sets up three choice menus, a message field, and a button. The control
part consists of a single call-back function, which is attached to the
single button in the window. It determines the (list of) current choice
indices, hands them over to build-number, and draws the result as
a string into the message field.
Let's study the organization of the call-back functions in more detail. It composes three kinds of functions:
Modify the program of figure
so that it implements the
number-guessing game from exercises
,
,
and
. Make sure that the number of digits that the player
must guess can be changed by editing a single definition in the program.
Hint: Recall that exercise
introduces a function that
generates random numbers. Solution
Exercise 22.3.2
Develop a program for looking up phone numbers. The program's GUI should consist of a text field, a message field, and a button. The text field permits users to enter names. The message field should display the number that the model finds or the message ``name not found'', if the model produces false.
Generalize the program so that a user can also enter a phone number (as a sequence of digits containing no other characters).
Hints: (1) Scheme provides the function string->symbol for converting a string to a symbol. (2) It also provides the function string->number, which converts a string to a number if possible. If the function consumes a string that doesn't represent a number, it produces false:
(string->number ``6670004'') = 6670004
(string->number ``667-0004'') = falseThe generalization demonstrates how one and the same GUI can use two distinct models.
Real-world GUIs: The graphical user interface in
figure
was not constructed from the items provided by
the teachpack. GUIs constructed with the teachpack's gui-items are
primitive. They are sufficient, however, to study the basic
principles of GUI programming. The design of real-world GUIs involves
graphics designers and tools that generate GUI programs (rather than making
them by hand). Solution
Exercise 22.3.3
Develop pad->gui. The function consumes a title (string) and a gui-table. It turns the table into a list of lists of gui-items that create-window can consume. Here is the data definition for gui-tables:
A cell is either
- a number,
- a symbol.
A gui-table is a (listof (listof cell)) .
Here are two examples of gui-tables:
(define pad
'((1 2 3)
(4 5 6)
(7 8 9)
(\# 0 *)))
(define pad2
'((1 2 3 +)
(4 5 6 -)
(7 8 9 *)
(0 = \. /)))
The table on the left lays out a virtual phone pad, the right one a calculator pad.
The function pad->gui should turn each cell into a button. The resulting list should be prefixed with two messages. The first one displays the title and never changes. The second one displays the latest button that the user clicked. The two examples above should produce the following two GUIs:
| | |
Hint: The second message header requires a short string, for example,
``N'', as the initial value. Solution