Java Arena
Step 6 of 6

Layouts inside layouts

Each layout so far was the root of its window, holding controls. But a layout is a node too, so it can be a child of another layout. Real windows are built like that: a BorderPane for the frame, with an HBox of buttons at the bottom, a VBox down one side, a GridPane in the middle.

A scoreboard, nested

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        HBox score = new HBox(20, new Label("Harbor Rovers"), new Label("2 : 1"), new Label("Mill Town"));
        score.setPadding(new Insets(10));
        score.setAlignment(Pos.CENTER);

        VBox goals = new VBox(4, new Label("12' Okafor"), new Label("40' Lindqvist"), new Label("77' Mbeki"));
        goals.setPadding(new Insets(10));

        HBox buttons = new HBox(8, new Button("Previous match"), new Button("Next match"));
        buttons.setPadding(new Insets(10));
        buttons.setAlignment(Pos.CENTER_RIGHT);

        BorderPane board = new BorderPane();
        board.setTop(score);
        board.setCenter(goals);
        board.setBottom(buttons);

        stage.setScene(new Scene(board));
        stage.setTitle("Scoreboard");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Scoreboard"
  BorderPane
    top: HBox spacing 20, padding 10, alignment CENTER
      Label "Harbor Rovers"
      Label "2 : 1"
      Label "Mill Town"
    center: VBox spacing 4, padding 10
      Label "12' Okafor"
      Label "40' Lindqvist"
      Label "77' Mbeki"
    bottom: HBox spacing 8, padding 10, alignment CENTER_RIGHT
      Button "Previous match"
      Button "Next match"

Look at the window as text: the HBox is in the top region, and its three labels are indented under it, one level deeper. Each layout looks after its own children only. The BorderPane decides where the HBox goes, and the HBox decides where its labels go inside it.

Building parts in helper methods

A window like this makes start long, and the parts get mixed up. Methods help, as they did in every program before: a method can build one part of the window and return it. Its return type is the class of the part (HBox, VBox, GridPane), or a more general one, such as Parent or Node. The same scoreboard:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        String[] scorers = {"12' Okafor", "40' Lindqvist", "77' Mbeki"};
        BorderPane board = new BorderPane();
        board.setTop(score("Harbor Rovers", 2, 1, "Mill Town"));
        board.setCenter(goals(scorers));
        board.setBottom(buttons());

        stage.setScene(new Scene(board));
        stage.setTitle("Scoreboard");
        stage.show();
    }

    private HBox score(String home, int homeGoals, int awayGoals, String away) {
        HBox score = new HBox(20, new Label(home), new Label(homeGoals + " : " + awayGoals), new Label(away));
        score.setPadding(new Insets(10));
        score.setAlignment(Pos.CENTER);
        return score;
    }

    private VBox goals(String[] scorers) {
        VBox goals = new VBox(4);
        goals.setPadding(new Insets(10));
        for (String scorer : scorers) {
            goals.getChildren().add(new Label(scorer));
        }
        return goals;
    }

    private HBox buttons() {
        HBox buttons = new HBox(8, new Button("Previous match"), new Button("Next match"));
        buttons.setPadding(new Insets(10));
        buttons.setAlignment(Pos.CENTER_RIGHT);
        return buttons;
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Scoreboard"
  BorderPane
    top: HBox spacing 20, padding 10, alignment CENTER
      Label "Harbor Rovers"
      Label "2 : 1"
      Label "Mill Town"
    center: VBox spacing 4, padding 10
      Label "12' Okafor"
      Label "40' Lindqvist"
      Label "77' Mbeki"
    bottom: HBox spacing 8, padding 10, alignment CENTER_RIGHT
      Button "Previous match"
      Button "Next match"
  • start now reads like a summary of the window: a score on top, the goals in the middle, buttons at the bottom.
  • The methods aren't static: they are methods of the Main object that JavaFX created, as start is.
  • score takes the teams and the goals as parameters, so the same method could build the line of any match.
  • goals takes an array of the scorers and makes a label for each, so it works for a match of any number of goals.

Each call of a helper method makes new nodes. That's what you want: since a node can be in one place at a time, a method that returned the same Label object on every call would move it from place to place, and the window would show it only once.

A form in a grid

A form is a grid: labels in the first column, fields in the second. With the form in the center of a BorderPane and the buttons in an HBox at the bottom, it's the shape of many windows. The Your turn below builds one.

Your task

Challenge 1 of 3

The library's form for a new card. Build it with two helper methods, form() and buttons(), which start already uses. The window's title is New library card, and its root is a BorderPane with:

  • top: the label Apply for a library card, in the font Font.font("Serif", 18)
  • center: the GridPane that form() returns, with the gaps 8 between both columns and rows and the padding 10 on every side. It holds the labels Name, Email and Branch in column 0, rows 0 to 2, and next to each label, in column 1, an empty TextField.
  • bottom: the HBox that buttons() returns, with the spacing 8, the padding 10 on every side and the alignment CENTER_RIGHT. It holds the buttons Cancel and Apply.

The window it should show is drawn below.

Challenge 2

Step 1's bus stop gets a board with all its departures. First the data. The file departures.txt has a line for each bus, such as 7:15;4;Harbor Square: the time, the line and where it goes, with semicolons between them. In BusTime.java, write the class BusTime:

  • a constructor BusTime(String clock, String route, String towards)
  • the methods getClock(), getRoute() and getTowards()
  • a static method fromText(String text), which returns a new BusTime made from a line of the file
  • toString(), which returns the departure like 7:15 line 4 to Harbor Square

Main.java is ready: it shows a VBox with a label for each of three departures, made with your class. The check runs:

BusTime bus = BusTime.fromText("7:15;4;Harbor Square");
System.out.println(bus.getRoute() + " leaves at " + bus.getClock());
System.out.println(bus);

and expects:

4 leaves at 7:15
7:15 line 4 to Harbor Square

Challenge 3

Now the board itself, with BusTime from the last challenge ready in BusTime.java. Build the window: the title is Stop 14 departures, and the root is a BorderPane with:

  • top: the label Departures from stop 14
  • center: a GridPane with the gaps 12 between columns and 4 between rows, and the padding 10 on every side. Row 0 holds the labels Time, Line and To, in columns 0 to 2. Below it, a row for each departure of departures.txt, in the file's order: labels with its time, its line and its destination, in the same columns.

Write a helper method addRow(GridPane grid, int row, String first, String second, String third) (it returns nothing) that adds the three labels of one row, and use it for every row, the first one too. For the file shown below, the window should look like the drawing under it.