Over the next few lectures we will learn how to create simple Graphical User Interfaces (also known as GUIs) for Scheme programs. The dialect of Scheme we use inherits its GUI-building primitives from Java. The current GUI-building library for Scheme is called Jlib. Scheme GUIs are created by first defining a collection of primitive components (e.g., buttons, labels, textfields, etc.). Next one "lays out" these components in a window by using the "row", "col", and "grid" containers. These containers can be nested, so you can have a row of grids or a column with two grids and a row. Finally, one associates actions to all of the components that can be activated by the user (e.g., buttons can be pushed, choices can be selected, etc.). We will go over these steps in detail in the next few lectures.
Components
The primitive components that are currently available in Jlib are the following. The first
three can have actions associated to them,
the last two simply appear in the window or the web page. The actions will be triggered when the
user interacts with the component in such a way as to generate an "action event." For buttons and
choices this is done using the mouse. For textfields, action events are generated by typing a
string into the textfield and hitting the return key.
(button TEXT)
-- where TEXT
is the string that should appear on the button
(textfield TEXT COLS)
-- where TEXT
is the string that should initially appear in the textfield,
and COLS
is the number of columns that should be displayed in the textfield.
(choice C1 C2 ... Cn)
-- where C1,..., Cn
are strings representing the possible choices the user
can select. The first choice C1
is the one that is initially displayed.
over a choice, the list of strings appears and the user can select one,
(label TEXT)
-- where TEXT
is the string that should appear on the label
(textarea ROWS COLS)
-- where ROWS
and COLS
give the size of the textarea
(window TITLE COMPONENTS)
-- where TITLE
appears in the window title bar.
(define B (button "push me")) (define T (textfield "type in here" 30)) (define C (choice "1st choice" "2nd choice" "3rd choice")) (define A (textarea 10 40)) (define L (label "this is a label")) (define W (window "Components" (col B T C A L))) (.pack W) (.show W)You may want to try this example using the Demorunner applet or using the Scheme interpreter that appears below.
white lightGray gray darkGray black red pink orange yellow green magenta cyan blueThus we can make a red button using
(define B1 (button "push me" red))Colors can also be specified numerically, by giving a numeric value for the amount of red, green, and blue light to use. These values must be between 0 and 255.
(define B2 (button "push me" (color 255 200 155)))In addition to background colors, one can specify the name, style, and size of the font using the following procedures:
(CourierPlain N) (CourierBold N) (CourierItalic N) (HelveticaPlain N) (HelveticaBold N) (HelveticaItalic N) (TimesRomanPlain N) (TimesRomanBold N) (TimesRomanItalic N)where N is the font size to use. Thus, we can create a tan button with a large Helvetica boldface font using:
(define B3 (button "push me" (color 255 200 155) (HelveticaBold 36)))
Containers
The most commonly used containers in JLIB are the following:
(row C1 C2 ... Cn)
C1 ...Cn
are components that will be arranged horizontally in the row container
(col C1 C2 ... Cn)
C1 ...Cn
are components that will be arranged horizontally in the col container
(table NUMROWS NUMCOLS C1 ... Cn)
NUMROWS
specifies the number of rows in the table,
NUMCOLS
specifies the number of columns in each row and the components
C1 ...Cn
are then added from left-to-right starting with the first row
and proceeding to the last.
'none 'horizontal 'vertical 'bothand where the component is placed in the container using
'north 'northeast 'east ... 'center
For example, the following Scheme program shows different ways of arranging three buttons:
(define R (row red (button "row one") (button "row two") (button "row three")))
(define C (col blue (button "col one") (button "col two") (button "col three")))
(define T23
(table 2 3 orange
(button "one") (button "two") (button "three") (button "four") (button "five")))
(define T32 (table 3 2 pink
(button "one") (button "two") (button "three") (button "four") (button "five")))
(define W (window "Components" (col 'none R C TR2 TC2 )))
(.pack W)
(.show W)
For example, the following Scheme program shows different ways of arranging three buttons:
(define R (row red (button "row one") (button "row two") (button "row three")))
(define C (col blue (button "col one") (button "col two") (button "col three")))
(define G13 (grid 1 3 green (button "g13 one") (button "g13 two") (button "g13 three")))
(define G31 (grid 3 1 yellow (button "g31 one") (button "g31 two") (button "g31 three")))
(define G22 (grid 2 2 orange (button "g22 one") (button "g22 two") (button "g22 three")))
(define T22 (table 2 2 orange (button "g22 one") (button "g22 two") (button "g22 three")))
(define W (window "Components" (col 'none R C G31 G13 G22)))
(.pack W)
(.show W)
(grid ROWS COLS C1 ... Cn)
As was mentioned above, containers can contain containers. For example, rather than
have a grid of components, one could use a row of columns, or a column of rows. It is
instructive to compare the differences between these three layouts. For example,
consider the following Scheme program:
(define RC (row red (col yellow (button "RC one") (button "RC two"))
(col green (button "RC three") (button "RC four"))))
(define CR (col red (row yellow (button "CR one") (button "CR two"))
(row green (button "CR three") (button "CR four"))))
(define G2 (grid 2 2 red (button "G2 one") (button "G2 two")
(button "G2 three") (button "G2 four")))
(define W (window "Components" (col RC CR G2)))
(.pack W)
(.show W)
Observe how the columns do not line up in the CR
example.
If the heights of the buttons were different then the we would
see that the rows of the RC
example would not line up.
The grid
containers aligns all rows and columns but
it has the also makes all grid cells the same size, which is not always
desirable.
Changing the Properties of Components
The components we have examined all have two default properties.
They have an initial color (which is gray by default) and an initial
font (which varies depending on the computer and browser). You can
change the color and font of any component by evaluating the following expressions:
(.setBackground COMPONENT COLOR)
COMPONENT
is the component whose color should be changed and
COLOR
is one of the color specifiers listed above (i.e.
either a named color red,green, ...
or a user specified color
(color RED GREEN BLUE)
where
RED
, GREEN
, and BLUE
are integers in the
range 0-255 which specify how much of each color should be used to create the desired color.
(.setfont COMPONENT FONTCONSTRUCTOR)
COMPONENT
specifies which component to change,
FONTCONSTRUCTOR
is one of the font constructors listed above,
e.g. (HelveticaBold 24)
.
For example, the following program creates threee buttons with different colors and fonts:
(define b1 (button "red Helvetica plain 12" red (HelveticaPlain 12)))
(define b2 (button "blue Courier bold 24" blue (CourierBold 24)))
(define b3 (button "yellow TimesRoman italic 6") (color 255 255 0) (TimesRomanItalic 6))
(define W (window 100 100 "Fonts/Colors"
(col b1 b2 b3)
(color 255 255 255)))
(.pack W)
(.show W)
(Thread.sleep 2000L)
(.setBackground b1 green)
(Thread.sleep 2000L)
(.setFont b2 (HelveticaBold 18))
(Thread.sleep 2000L)
(.setBackground W red)
Reading and Writing Components
Scheme offers various procedures for reading the text on labels,
buttons, textfields, textareas, and choices. It also provides
procedures for changing the text on all but the last.
There are three procedures for reading text from components:
(readstring COMPONENT)
(readexpr COMPONENT)
1729
will return that number, whereas readString
would return "1729".
The main difference is that the arithmetic operators (+,-,*,/, etc.) can
be applied to a number, but not to a string.
(readexprlist COMPONENT)
1 2 3 4
. Reading this as a string would
return "1 2 3 4", reading it using readExpr
returns just
the first number (1), and reading it using readList
returns
the list (1 2 3 4)
. We will discuss lists in more detail
in a later lecture.
(writeexpr COMPONENT EXPRESSION)
(appendexpr COMPONENT EXPRESSION)
(writelnexpr COMPONENT EXPRESSION)
(appendlnexpr COMPONENT EXPRESSION)
(appendexpr A "a") (appendexpr A "b") ...
where A is
a textarea, then you will get the alphabet appearing on one line. If you evaluate
the same expressions but using appendlnexpr
instead of appendexpr
then you would get one letter per line. If you used writeexpr
or writelnexpr
then each new letter would erase the previous letter.
(define B (button "push me")) (define T (textfield "type in here" 30)) (define C (choice "1st choice" "2nd choice" "3rd choice")) (define A (textarea 10 40)) (define L (label "this is a label")) (define W (window "Components" (col B T C A L))) (.pack W) (.show W)
So we can just write strings or numbers to components:
(writeexpr B "Ouch")
(appendexpr T ", yes right here")
(writelnexpr A (* 33 37))
(writeexpr L "The Main Label")
We can get a billboard effect by using
(sleep N)
procedure,
which simply does nothing for N milliseconds
(writeexpr B "Ouch")
(appendexpr T ", yes right here")
(writelnexpr A (* 33 37))
(writeexpr L "The Main Label")
(Thread.sleep 2000L)
(writeexpr B "push me")
(writeexpr T "type in here")
(writeexpr A "")
(writeexpr L "this is a label")
Evaluating these expressions, causes all the labels to change and then
to be reset 2 seconds later.
We can also read strings from one component and write them on another:
(appendlnexpr A (readstring T))
Evaluating this expression causes the text in the textfield T to be read
into a string, and that string is then appended to the textarea A.
Repeatedly evaluating this causes the text to be written several times.
If this piece of code were the action associated to the textfield, then
whenever something was entered in the textfield, it would appear in the
textarea after the user hit the return key.