Designing GUIs in Scheme I:
Components and Layouts


Contents

  1. Components
  2. Containers
  3. Changing Properties of Components
  4. Reading and Writing Components


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. These components are created in Scheme by evaluating one of the following expressions with the proper arguments: For example, the following Scheme program creates a window containing an example of each of the five components:
    (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.

Component Properties

Each of these components can be customized by specifying a background color and font after the arguments shown above. The recognized colors are:

 white lightGray gray darkGray black red pink orange yellow green magenta cyan blue

Thus 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: Note that the container objects are also components and so can appear inside other containers. These containers are created using the following Jlib procedures: As with components, one can specify the background color and font of these containers. One can also specify in which directions the components expand when the container is resized using:
 
  'none 'horizontal 'vertical 'both
and 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)

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:

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:

There are two procedures which are commonly used for writing text onto components For text areas there are two other useful procedures which write or append text and then skip to the next line: If you evaluate (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.

Examples of reading/writing on components

For these examples, we assume you have first evaluated the following expressions, which create a window containing components of various types:
    (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.