1.1 Windowing
The PLT Scheme windowing toolbox provides the basic building blocks of GUI programs, including frames (top-level windows), modal dialogs, menus, buttons, check boxes, text fields, and radio buttons. The toolbox provides these building blocks via built-in classes, such as the frame% class:
| ; Make a frame by instantiating the frame% class |
| (define frame (new frame% [label "Example"])) |
| ; Show the frame by calling its show method |
| (send frame show #t) |
The built-in classes provide various mechanisms for handling GUI events. For example, when instantiating the button% class, the programmer supplies an event callback procedure to be invoked when the user clicks the button. The following example program creates a frame with a text message and a button; when the user clicks the button, the message changes:
| ; Make a frame by instantiating the frame% class |
| (define frame (new frame% [label "Example"])) |
| ; Make a static text message in the frame |
| (define msg (new message% [parent frame] |
| [label "No events so far..."])) |
| ; Make a button in the frame |
| (new button% [parent frame] |
| [label "Click Me"] |
| ; Callback procedure for a button click: |
| (callback (lambda (button event) |
| (send msg set-label "Button click")))) |
| ; Show the frame by calling its show method |
| (send frame show #t) |
Programmers never implement the GUI event loop directly. Instead, the system automatically pulls each event from an internal queue and dispatches the event to an appropriate window. The dispatch invokes the window’s callback procedure or calls one of the window’s methods. In the above program, the system automatically invokes the button’s callback procedure whenever the user clicks Click Me.
If a window receives multiple kinds of events, the events are dispatched to methods of the window’s class instead of to a callback procedure. For example, a drawing canvas receives update events, mouse events, keyboard events, and sizing events; to handle them, a programmer must derive a new class from the built-in canvas% class and override the event-handling methods. The following expression extends the frame created above with a canvas that handles mouse and keyboard events:
| ; Derive a new canvas (a drawing window) class to handle events |
| (define my-canvas% |
| (class canvas% ; The base class is canvas% |
| ; Define overriding method to handle mouse events |
| (define/override (on-event event) |
| (send msg set-label "Canvas mouse")) |
| ; Define overriding method to handle keyboard events |
| (define/override (on-char event) |
| (send msg set-label "Canvas keyboard")) |
| ; Call the superclass init, passing on all init args |
| (super-new))) |
| ; Make a canvas that handles events in the frame |
| (new my-canvas% [parent frame]) |
After running the above code, manually resize the frame to see the new canvas. Moving the cursor over the canvas calls the canvas’s on-event method with an object representing a motion event. Clicking on the canvas calls on-event. While the canvas has the keyboard focus, typing on the keyboard invokes the canvas’s on-char method.
The system dispatches GUI events sequentially; that is, after invoking an event-handling callback or method, the system waits until the handler returns before dispatching the next event. To illustrate the sequential nature of events, we extend the frame again, adding a Pause button:
| (new button% [parent frame] |
| [label "Pause"] |
| [callback (lambda (button event) (sleep 5))]) |
After the user clicks Pause, the entire frame becomes unresponsive for five seconds; the system cannot dispatch more events until the call to sleep returns. For more information about event dispatching, see Event Dispatching and Eventspaces.
In addition to dispatching events, the GUI classes also handle the graphical layout of windows. Our example frame demonstrates a simple layout; the frame’s elements are lined up top-to-bottom. In general, a programmer specifies the layout of a window by assigning each GUI element to a parent container. A vertical container, such as a frame, arranges its children in a column, and a horizontal container arranges its children in a row. A container can be a child of another container; for example, to place two buttons side-by-side in our frame, we create a horizontal panel for the new buttons:
| (define panel (new horizontal-panel% [parent frame])) |
| (new button% [parent panel] |
| [label "Left"] |
| [callback (lambda (button event) |
| (send msg set-label "Left click"))]) |
| (new button% [parent panel] |
| [label "Right"] |
| [callback (lambda (button event) |
| (send msg set-label "Right click"))]) |
For more information about window layout and containers, see Geometry Management.
1.1.1 Core Windowing Classes
The fundamental graphical element in the windowing toolbox is an area. The following classes implement the different types of areas in the windowing toolbox:
Containers – areas that can contain other areas:
frame% – a frame is a top-level window that the user can move and resize.
dialog% – a dialog is a modal top-level window; when a dialog is shown, other top-level windows are disabled until the dialog is dismissed.
panel% – a panel is a subcontainer within a container. The toolbox provides three subclasses of panel%: vertical-panel%, horizontal-panel%, and tab-panel%.
pane% – a pane is a lightweight panel. It has no graphical representation or event-handling capabilities. The pane% class has three subclasses: vertical-pane%, horizontal-pane%, and grow-box-spacer-pane%.
Containees – areas that must be contained within other areas:
panel% – a panel is a containee as well as a container.
pane% – a pane is a containee as well as a container.
canvas% – a canvas is a subwindow for drawing on the screen.
editor-canvas% – an editor canvas is a subwindow for displaying a text editor or pasteboard editor. The editor-canvas% class is documented with the editor classes in Editor.
Controls – containees that the user can manipulate:
message% – a message is a static text field or bitmap with no user interaction.
button% – a button is a clickable control.
check-box% – a check box is a clickable control; the user clicks the control to set or remove its check mark.
radio-box% – a radio box is a collection of mutually exclusive radio buttons; when the user clicks a radio button, it is selected and the radio box’s previously selected radio button is deselected.
choice% – a choice item is a pop-up menu of text choices; the user selects one item in the control.
list-box% – a list box is a scrollable lists of text choices; the user selects one or more items in the list (depending on the style of the list box).
text-field% – a text field is a box for simple text entry.
combo-field% – a combo field combines a text field with a pop-up menu of choices.
slider% – a slider is a dragable control that selects an integer value within a fixed range.
gauge% – a gauge is an output-only control (the user cannot change the value) for reporting an integer value within a fixed range.
As suggested by the above listing, certain areas, called containers, manage certain other areas, called containees. Some areas, such as panels, are both containers and containees.
Most areas are windows, but some are non-windows. A window, such as a panel, has a graphical representation, receives keyboard and mouse events, and can be disabled or hidden. In contrast, a non-window, such as a pane, is useful only for geometry management; a non-window does not receive mouse events, and it cannot be disabled or hidden.
Every area is an instance of the area<%> interface. Each container is also an instance of the area-container<%> interface, whereas each containee is an instance of subarea<%>. Windows are instances of window<%>. The area-container<%>, subarea<%>, and window<%> interfaces are subinterfaces of area<%>.
The following diagram shows more of the type hierarchy under area<%>:
______________________|_______________ |
| | | |
|____ _______|__________ | |
| | | | |
________|________ | |
| | | |
The diagram below extends the one above to show the complete type hierarchy under area<%>. (Some of the types are represented by interfaces, and some types are represented by classes. In principle, every area type should be represented by an interface, but whenever the windowing toolbox provides a concrete implementation, the corresponding interface is omitted from the toolbox.) To avoid intersecting lines, the hierarchy is drawn for a cylindrical surface; lines from subarea<%> and subwindow<%> wrap from the left edge of the diagram to the right edge.
_____________________|_______________ |
| | | |
<<<____|____ _____|__________ __|___ ___________________<<< |
| | | | | | |
subwindow<%> | | | | |
<<<______________|___________ | | | | _<<< |
| | | | pane% | |
control<%> | | | |- horizontal-pane% | |
|- message% | | | |- vertical-pane% | |
|- button% | | | | |
|- slider% | | | |
|- gauge% | | __________________| |
|- text-field% | | | |
|- combo-field% | |-------- panel% |
|- radio-box% | | |- horizontal-panel% |
|- list-control<%> | | |- vertical-panel% |
|- choice% | | |- tab-panel% |
|- list-box% | | |- group-box-panel% |
| | |
| |- top-level-window<%> |
| |- frame% |
|- canvas% |
Menu bars, menus, and menu items are graphical elements, but not areas (i.e., they do not have all of the properties that are common to areas, such as an adjustable graphical size). Instead, the menu classes form a separate container–containee hierarchy: