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 is 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 the user interface as a scene graph, 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.

Node class hierarchy

Building a GUI

A scene graph must be constructed. This can be done either by either

All of the non-leaf nodes in the tree are a subclass of Parent (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 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.

A scene graph can also be created in advance using the JavaFX SceneBuilder application. SceneBuilder saves the scene is saved in XML format in an .fxml file, which can be loaded by the application to create the entire scene graph at once.

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. The classes 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

There are several ways to control the appearance of the scene. Some nodes have handles that allow them to be minimized, maximized, or resized. There are also various controls such as sliders or knobs to control shape, size, color, or other aspect of the appearance. Nodes can also be added, removed, moved, or transformed. With JavaFX, when such a change is made and the scene needs to be redrawn, the visible components of the scene redraw themselves in the right order.

A useful feature of JavaFX is that nodes can be styled using style sheets (CSS), similar to how HTML pages are styled (e.g., p.setStyle("-fx-background-color: #ffff00");). The style sheet can also be created in advance and loaded from an external file. Style sheets provide an easy way to rapidly experiment with different looks for an application without recompiling the application.

It is also possible to draw onto a Canvas using a GraphicsContext object. One can draw shapes such as rectangles, circles, ellipses, or curves. Canvases give complete control over rendering anything within their borders, but the program has the responsibility for the rendering. One difference from Swing, JavaFX's predecessor, is that exposed pixels are redrawn automatically.

Event-driven programs

JavaFX applications are typically event driven. This is a somewhat different programming paradigm from the usual imperative style. Its motto is: "Don't call us, we'll call you." When some hardware event takes place such as a mouse click or key press, an Event object is generated. An Event object is simply a Java object like any other Java object. It contains information about the source and type of the event: button click, mouse movement, keystroke, etc. The event handler of any listener that has registered to receive the event is called, and the event object is passed to the handler. In that way an application can receive and respond to events such as mouse clicks and button pushes.

Threads

Unlike simple Java programs, a JavaFX application typically has multiple concurrent threads: a main thread, a application thread, and a rendering thread, as well as various background worker threads. The application thread's sole task is to loop continuously, listening for the occurrence of hardware-generated events such as mouse clicks. For each such occurrence, it constructs an Event object describing the event and the GUI component in which it occurred, then calls the event handler of each registered listener with that Event object. The rendering thread's task is to continuously rerender the display.

Event handlers should not contain long-running computations, because this will tie up the application thread, preventing events from being handled. This will appear to cause the GUI to freeze and not respond to events. Fortunately, there is a mechanism for scheduling an update of the GUI on the application thread later using the method Platform.runLater(Runnable r). The run() method of the Runnable object r is called at the earliest available opportunity by the application thread.

Handling events

Programs can define event handlers to handle different types of input events. Handlers must be registered to receive events with a GUI component on which the event can occur. There are several ways to specify a handler: classes, inner classes, or lambda expressions. All are basically syntactic sugar for the same thing, but convenient. Here is an example with a 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 registered handler. Lambda expressions are really just syntactic sugar for inner classes, which are anonymous classes implementing a specified interface. In this case, the method setOnAction expects to receive an object implementing the interface EventHandler<ActionEvent>, where EventHandler is defined as follows:

EventHandler.java

It is not necessary to use a lambda expression to implement an event handler. The lambda expression is syntactic sugar for the following code defining an inner class:

desugared_lambda.java
It is not even necessary to use an inner class; an ordinary class can be used to implement the desired interface, as in the following code:
desugared_lambda.java

In fact, even inner classes are syntactic sugar for top-level classes. Because they are just syntactic sugar, they come with some limitations. In particular, inner classes and lambda expressions can only refer to variables defined outside their scope if those variables are final. The reason for this limitation is that their objects may outlive the external variables they refer to. For that reason, they make a copy of any such external variable.