Graphical User Interfaces: Display and Layout

One of the driving forces behind the development of object-oriented programming was the need to create graphical user interfaces (GUIs). It's not surprising, then, that the OO programming model really shines when building GUIs.

In fact, the standard WIMP model of graphical user interfaces (Windows, Icons, Menus, Pointer) was developed at Xerox PARC during the 1970's in and along with one of the first object-oriented programming languages, Smalltalk. GUIs influenced the language design and vice versa. The WIMP model first saw widespread deployment by Apple with the first Macintosh computer and was soon adopted by Microsoft Windows as well.

In this approach to graphical user interface design, the user interface is represented internally as a tree structure, in which the various user interface components (sometimes called widgets) are nodes in the tree, and the parent–child relationship corresponds to containment. In fact, the HTML markup language follows this same approach to defining a user interface.

We will be using JavaFX, a modern object-oriented GUI library. In such a library, there are two interacting hierarchies: first, the containment hierarchy corresponding to the tree of UI components, and second, the class hierarchy of the components themselves. Unlike in HTML, the class hierarchy is extensible, allowing developers to use inheritance to design new components or to customize the behavior of existing components.

Scene Graph

JavaFX manages user interfaces as scene graphs, which is actually a tree of nodes. At the root of the scene graph is some node; the node is registered with a Scene object that is in turn registered with a Stage, which corresponds to a top-level window in the application.

Example

(These notes still in progress)

Node class hierarchy

Building a UI

A scene graph must be constructed. This can be done either by writing code to build the scene graph one node at a time, or by reading it from a file. All of the non-leaf nodes in the tree will be of class Parent or some subclass (typically a subclass of Pane).

To add a child using code:

Parent p;
Node n;
p.getChildren().add(n);

The getChildren() method returns a collection of children that is tied to the actual children of the parent node. Thus, adding a new node to the collection causes the parent node to acquire a new child. The collection can can be used in various other ways, however; for example, it can be used to iterate over the children or to listen to its contents to find out when the set of children changes.

The JavaFX Scene Builder can also be used to create a scene graph that can then be saved into a .fxml file to be loaded later.

Layout

Some JavaFX nodes just display themselves (e.g., Rectangle). Some nodes do something active (e.g., Button, TextField). Other nodes are just there to control the layout of other nodes in the window. Examples of these are the various subclasses of Pane. Pane places all its children in the upper left corner. HBox and VBox lay out their children in a horizontal row or vertical column, respectively. StackPane stacks all its children on top of each other, centered. GridPane, FlowPane, and BorderPane lay out children in fancier ways.

Appearance

  1. Can use existing nodes (controls, shape nodes)
  2. Can draw onto a Canvas using a GraphicsContext object
(These notes still in progress)

Event-driven programs

A different programming paradigm: “Don't call us, we'll call you.” JavaFX library invokes your application code with things to do.

Threads

JavaFX program has multiple threads, unlike simple Java programs: Main thread, event dispatching thread, rendering thread(s), background worker threads. Event loop in library waits for input, calls your application code with events to handle. Can do work in background by creating worker threads.

Long-running computation should not happen in the event dispatching thread, because this will freeze the UI by preventing events from being handled. To prevent interference between threads accessing the node graph, however, all access to the node graph has to happen in the event dispatching thread. Fortunately, you can cause changes to the UI from a thread other than the event dispatching thread by using the method Platform.runLater(). It causes some code to run (later, but usually very soon) in the event dispatching thread.

Events

Program defines event handlers to handle different input events. These can be defined using classes, inner classes, or lambda expressions. All are basically syntactic sugar for the same thing, but convenient. Example with lambda expression:

Button b = ...;
b.setOnAction(e -> print("button clicked"));

This is essentially the Observer pattern, but for many events, there can be only one handler attached, rather than allowing multiple handlers to be registered.