Learn Java

Lesson 78Elective: Desktop apps with JavaFX

Nodes, controls, and layout panes

The Node class family, choosing a root node, and the controls and panes this module's programs are built from.

By Updated 60 min

  • Node
  • Parent
  • Group
  • Region
  • Pane
  • Control

Every JavaFX program in this course is assembled from the same small set of parts. The registration form, the four calculator programs and the two database screens share about a dozen classes, and nearly all of them hang from one family tree that starts at Node. This lesson draws that tree, explains the choice of root that decides how a window behaves when it is resized, and then takes each part the programs use in turn. When those parts turn up in a hundred-line listing, you will already know what every line is for.

By the end of this lesson
  • Draw the Node class hierarchy and say what each class adds to the one above it.
  • Choose a root for a scene (a Group, a Region or a WebView) and predict what the window does when it is resized.
  • Say how each common pane arranges its children, and pick the right one for a layout.
  • Create, read and change every control the JavaFX programs here use, from Label to Alert.
  • Place nodes in a GridPane by column and row, with spans, gaps, column constraints and margins.
  • Read Insets, Pos and HPos values at a glance.
From before
Three ideas from earlier phases carry this whole lesson. From inheritance: a subclass is a kind of its superclass, so its objects can go anywhere the superclass is expected. From polymorphism: a method called through that superclass type still runs the subclass's own version. From abstract classes: a class you cannot instantiate, written only to be extended. Add the three steps of every start method from your first JavaFX application (build a scene graph, put it in a Scene, put the scene on the Stage) and you have everything this lesson needs.

The Node family tree#

Two words carry this whole lesson. A scene graph is the tree of everything in one window. A node is one object in that tree: a shape, a control, a pane, an image. The class Node, in the package javafx.scene, is the superclass of all of them, and the diagram below draws the family.

NodeShape2DCanvasParentGroupRegionChartPaneControlWebViewImageViewShape 3D
The Node class hierarchy. Each arrow points from a subclass up to the class it extends. The box called Shape2D is the class Shape, and Shape 3D is Shape3D.

Read each arrow as “extends”. Canvas extends Node. Group extends Parent. Control extends Region, which extends Parent, which extends Node. Two boxes carry informal names: there is no class called Shape2D (the two-dimensional shapes extend Shape, in javafx.scene.shape), and the three-dimensional box is the class Shape3D, written without a space.

BoxWhat it adds to the class above itExamples
Node, in javafx.sceneThe root of everything visible. A node has a position, can be hidden, faded, rotated or scaled, given an effect such as a shadow, styled with CSS, and can react to the mouse and the keyboard. Abstract.None directly: you always create a subclass.
Shape2D, really Shape, in javafx.scene.shapeA geometric figure with a fill colour and an outline (its stroke). Abstract.Circle, Rectangle, Ellipse, Line, Polygon, and Text
Canvas, in javafx.scene.canvasA blank picture of fixed size that you draw on with commands such as “fill this rectangle”. However much you draw, it stays one node.A drawing area, a simple game board
ImageView, in javafx.scene.imageShows an Image loaded from a file or a web address.A photo, a logo
Shape 3D, really Shape3D, in javafx.scene.shapeA solid in three dimensions, lit and shaded. Abstract.Box, Sphere, Cylinder
Parent, in javafx.sceneA list of child nodes, and the job of laying them out. Abstract.None directly
Group, in javafx.sceneLets you add children through getChildren(). Draws them in order and applies its own effects and transforms to all of them, but never moves them or stretches them (a resizable child simply gets its preferred size), and cannot be resized itself.The root in your first JavaFX programs
Region, in javafx.scene.layoutA size that can change (a minimum, preferred and maximum width and height), plus padding, a background and a border, all styleable with CSS.new Region() as an empty spacer
WebView, in javafx.scene.webA web browser engine inside the scene, showing pages written in HTML, CSS and JavaScript.A help page inside an application
Chart, in javafx.scene.chartDraws data, with a title and a legend. Abstract.PieChart, BarChart, LineChart
Pane, in javafx.scene.layoutLets you add children through getChildren(), which Region keeps to itself. Each subclass adds one rule for arranging them.VBox, HBox, GridPane, StackPane, BorderPane
Control, in javafx.scene.controlSomething the user works with. It takes keyboard focus (Tab moves between controls), can show a tooltip, and gets its look from a skin that CSS can restyle. Abstract.Button, Label, TextField, PasswordField, TextArea
What each box in the hierarchy contributes. A class has everything listed for the boxes above it on its branch, plus its own row.

The word “tree” gets used for two different things in JavaFX, and it pays to keep them apart. The diagram above is a tree of classes: it says that a Button is a kind of Control. A scene graph is a tree of objects: it says that this particular VBox holds this particular Button. The three types of node, root, branch and leaf, are positions in the second tree, and the first tree decides who may stand where. Only a Parent has a list of children, so only a Group, a Region or a WebView can be a root or a branch. Nodes such as a Rectangle, an Ellipse, a Box, an ImageView and a MediaView can only ever be leaves. The root is the first node of the scene graph, the one at the top.

One row deserves a second look. The Textclass that drew “Welcome to Ooty” in your first JavaFX application lives in javafx.scene.text, but it extends Shape, so in the hierarchy it belongs under the Shape2D box beside Circle and Rectangle. That is why a Text is placed with setX and setY like any shape, and why it has a fill colour. A Label looks much the same on screen but is a Control, and the difference shows as soon as either one goes into a layout pane. The two are compared side by side further down.

Six of the boxes are abstract classes: Node, Parent, Shape, Shape3D, Chart and Control. Writing new Node() or new Control() does not compile. They exist to hold what their subclasses share, exactly like the abstract classes you wrote yourself. Group, Region, Pane, Canvas, ImageView and WebView are concrete, so you can create them directly.

The tree is also why JavaFX code reads so evenly. A method whose parameter type is Node accepts an object of any class on it, because of upcasting: a Button is a Control, which is a Region, which is a Parent, which is a Node. So root.getChildren().add(...), new VBox(10, ...), grid.add(..., 0, 1) and borderPane.setCenter(...) all take a button, a text, an image, or a whole GridPane full of controls, through the same parameter. The Scene constructor is stricter. Its first parameter is a Parent, so only the Parent branch can be a root, and that branch holds exactly three kinds of box.

Key idea

Everything you can see is a Node, so anything that accepts a Node accepts all of it. A scene's root must be a Parent, which leaves three kinds: a Group, a Region, or a WebView.

Choosing a root: Group, Region or WebView#

Every scene has exactly one root, the node at the top of its scene graph, and it is not optional: Scene has no constructor without a root, and new Scene(null) throws a NullPointerException. The root can be a Group, a Region or a WebView. Your first JavaFX application used a Group. Which one you pick decides what happens when the user drags the edge of the window.

A Group root leaves everything where you put it#

The rule for a Group root has two halves: every node is clipped to the scene, and changing the size of the scene does not change the layout.

  • Clipped to the scene.The window shows the rectangle from (0, 0) to the scene's width and height, and nothing outside it. A Text placed at x = 550 in a 600-wide scene shows its first letter or so and is cut off at the right edge. There is no scroll bar: the rest is simply not drawn.
  • Resizing does not change the layout. A Group never moves its children and never stretches them. Each one stays at the coordinates it was given, or at (0, 0) if it was given none, at its preferred size. The Group cannot be resized either, because its size is just the box around its children. Make the window bigger and the new space stays empty; make it smaller and the window edge cuts into your nodes.

A Region root follows the window#

If the root is a Region (any pane, any control, a chart), the scene resizes it to fill the window every time the window changes size, and the region then re-arranges its children by its own rule. A StackPane re-centres them, a VBox stretches them across the new width, and a GridPane hands the extra width to a column that is allowed to grow. That is why every program after your first one uses a pane as its root: a GridPane for the registration form and the calculators, a VBox for the database screens.

The third choice, a WebView root, turns the whole window into a web page. A WebView resizes with the window too, and the page reflows to the new width.

One experiment shows the difference#

Here is a button in a Group root, in a window the user can resize.

GroupRoot.java
read along
1import javafx.application.Application;
2import javafx.scene.Group;
3import javafx.scene.Scene;
4import javafx.scene.control.Button;
5import javafx.stage.Stage;
6
7public class GroupRoot extends Application {
8 @Override
9 public void start(Stage stage) {
10 Button button = new Button("Click me");
11
12 Group root = new Group(button);
13 Scene scene = new Scene(root, 300, 200);
14
15 stage.setTitle("Group root");
16 stage.setScene(scene);
17 stage.show();
18 }
19
20 public static void main(String[] args) {
21 launch(args);
22 }
23}
Output
A 300 by 200 window titled "Group root". The button sits in the
top-left corner at its natural size. Drag the window out to twice
the size: the button stays exactly where it was, and the new space
is empty white.

Now change the two lines that mention Group, and nothing else.

StackRoot.java (the two changed lines)
read along
1import javafx.scene.layout.StackPane; // instead of javafx.scene.Group
2
3StackPane root = new StackPane(button); // instead of new Group(button)
Output
The button sits in the middle of the window. Drag the window out
and the button moves with it, staying in the middle. It keeps its
natural size, because a Button does not grow past its preferred
size unless you raise its maximum.
Group root
Click me
The Group root after the window is dragged larger. The button has not moved from the top-left corner, and the rest of the window shows the scene's default white fill.
StackPane root
Click me
The StackPane root after the same drag. The pane grew with the window and put the button back in the centre. The light grey comes from the default stylesheet, which gives a Region root a background.
Key idea

A Group root keeps the layout you built, whatever the window does. A Region root rebuilds the layout every time the window changes size, which is what a form needs.

The Region family: panes, controls and charts#

Region is the base class for the resizable parts of a window: almost every layout pane and control inherits from it. The properties it gives them are these, and every pane and control you use has them because it inherits them.

  • Three sizes in each direction: a minimum, a preferred and a maximum width (setMinWidth, setPrefWidth, setMaxWidth), and the same for height. A pane asks each child for these and chooses its size between them. The form's setPrefHeight(40) on its text fields is this.
  • Padding: empty space between the region's edge and its contents, set with setPadding(new Insets(...)).
  • A background and a border, which you can set from Java or with CSS, for example pane.setStyle("-fx-background-color: white; -fx-border-color: gray;").

Region is concrete, so new Region() works. On its own it is an empty box, and its one everyday use is as an invisible spacer that soaks up spare room in a row.

Pane and the layout panes#

Pane, in javafx.scene.layout, is the base class of the layout panes: AnchorPane, BorderPane, GridPane, FlowPane, StackPane, TilePane and more. A plain Pane arranges nothing: like a Group, it leaves each child at its own coordinates, although unlike a Group it can be resized and styled. Each subclass adds one rule for arranging children, so choosing a pane means choosing the rule.

PaneHow it arranges its childrenTypical use
AnchorPanePins a child a fixed distance from the edges you choose, with calls such as AnchorPane.setTopAnchor(node, 10.0). An anchored side follows its edge when the window is resized.A button that must stay in the bottom-right corner. Scene Builder's default root.
BorderPaneFive regions: top, bottom, left, right and centre. The centre gets all the space the other four do not use.The outline of a whole application: toolbar on top, status line at the bottom, content in the middle.
GridPaneRows and columns. Each child goes in a cell given as (column, row) and may span several cells.Forms, with labels in one column and fields in the next: the registration form and every calculator.
FlowPaneLeft to right in a row, wrapping onto a new row when it reaches the edge, like words in a paragraph.A set of tags or buttons that should wrap when the window narrows.
StackPaneAll children in the same space, one on top of another, centred by default. Later children are drawn over earlier ones.Centring one node in the window, or a caption over a picture.
TilePaneLike FlowPane, but every cell (tile) is the same size, the size of the largest child.A keypad, a grid of thumbnails.
VBoxOne column, top to bottom, with a fixed gap between neighbours.StudentApp's column of buttons above its output area.
HBoxOne row, left to right, with a fixed gap between neighbours.A row of buttons, such as Save beside Cancel.
Six common panes, plus VBox and HBox, which the database programs use. All eight extend Pane.

Here is code for one of these panes. A StackPane puts its children one on top of another, like a stack of cards.

StackPane
read along
1//Creating a Stack Pane
2StackPane pane = new StackPane();
3
4//Adding text area to the pane
5ObservableList list = pane.getChildren();
6list.add(NodeObject);
Output
Nothing to see on its own: NodeObject stands for whichever node you
add, and the pane still has to become the root of a Scene.

pane.getChildren() hands back the pane's live list of children, an ObservableList, and adding a node to that list puts the node on the pane. These are the same two lines your first JavaFX application used with a Group. The comment mentions a text area, but NodeObject is a placeholder for any node. (Because the list is written without <Node> after it, javac prints a harmless note about unchecked operations, the same note DisplayingText produced.) Here is a complete program with two children, so you can see the stacking.

StackDemo.java
read along
1import javafx.application.Application;
2import javafx.scene.Scene;
3import javafx.scene.layout.StackPane;
4import javafx.scene.paint.Color;
5import javafx.scene.shape.Rectangle;
6import javafx.scene.text.Font;
7import javafx.scene.text.Text;
8import javafx.stage.Stage;
9
10public class StackDemo extends Application {
11 @Override
12 public void start(Stage stage) {
13 Rectangle card = new Rectangle(220, 100, Color.LIGHTBLUE);
14 Text label = new Text("On top");
15 label.setFont(new Font(24));
16
17 StackPane pane = new StackPane();
18 pane.getChildren().addAll(card, label); // card first, so it is underneath
19
20 stage.setTitle("StackPane");
21 stage.setScene(new Scene(pane, 400, 250));
22 stage.show();
23 }
24
25 public static void main(String[] args) {
26 launch(args);
27 }
28}
Output
A 400 by 250 window titled "StackPane". A light blue 220 by 100
rectangle sits in the centre, with the words "On top" centred on it.
Resize the window and both stay in the centre.
StackPane
On top
Both children share the centre of the pane. The rectangle came first in the list, so the text is drawn over it.

Order matters: the rectangle is first in the list, so it is drawn first and the text lands on top of it. Swap the two in addAll and the rectangle hides the text. With a text area as the only child, the area would fill the whole window, because a StackPane stretches each resizable child to its own size, as far as the child's maximum allows, and centres whatever it cannot stretch. A Rectangle and a Text are not resizable, so they keep their size and sit in the middle.

Control and the common controls#

The other big branch under Region is Control, in javafx.scene.control: the base class of everything the user clicks, types into or picks from, such as Button, Label, TextField, CheckBox, ComboBox, ChoiceBox and Accordion.

ControlWhat the user sees and doesHow your code reads it
ButtonA push button with a caption. Clicking it fires an action.Nothing to read: setOnAction(...) runs your code when it is pressed.
LabelA piece of text the user cannot edit, usually naming the field beside it.getText()
TextFieldOne line of text the user can type into.getText() returns a String
CheckBoxA small box with a caption, ticked or not.isSelected() returns a boolean
ComboBoxA drop-down list. It can also be made editable, so the user may type a value instead.getValue() returns the chosen item, or null if none
ChoiceBoxA simpler drop-down for a short, fixed list.getValue(), or null if none
AccordionA stack of titled panels. Opening one closes the panel that was open.getExpandedPane() returns the open panel
Seven common controls. The JavaFX programs in this course use the first three.
Controls.java (creating each one)
read along
1Button save = new Button("Save");
2Label nameLabel = new Label("Name:");
3TextField nameField = new TextField();
4CheckBox agree = new CheckBox("I agree to the terms");
5
6ComboBox<String> branch = new ComboBox<>();
7branch.getItems().addAll("CSE", "IT", "ECE");
8
9ChoiceBox<String> year = new ChoiceBox<>();
10year.getItems().addAll("1", "2", "3", "4");
11
12Accordion help = new Accordion(
13 new TitledPane("Login", new Label("Use your school email.")),
14 new TitledPane("Fees", new Label("Pay before the 10th.")));
Output
Placed in a VBox, top to bottom: a Save button, the text "Name:",
an empty field, an unticked box captioned "I agree to the terms",
a drop-down that lists CSE, IT and ECE when opened, a smaller one
listing 1 to 4, and two closed panels titled Login and Fees.

The <String> after ComboBox and ChoiceBox says what kind of item the list holds, so getValue() hands back a String. getItems() is the list of choices, and addAll fills it. Of these seven, the programs in this course use Button, Label and TextField, and the next section covers those three properly.

Chart: PieChart and XYChart#

Chart, in javafx.scene.chart, is the base class of every chart. It has two subclasses, PieChart and XYChart. The charts drawn on x and y axes, AreaChart, BarChart, BubbleChart and the rest, all extend XYChart. PieChart has no subclasses: a pie has no axes.

  • AreaChart: values joined by a line, with the area underneath filled in.
  • BarChart: one bar per category, its height showing the value.
  • BubbleChart: points drawn as circles, whose size shows a third value.
  • LineChart: points joined by straight lines, good for change over time.
  • ScatterChart: points with no lines between them.

The XY charts need two axes, such as a CategoryAxis for names along the bottom and a NumberAxis for values up the side (new BarChart<>(new CategoryAxis(), new NumberAxis())), and they take their points as XYChart.Data objects grouped into an XYChart.Series. A PieChart needs none of that, which makes it the quickest chart to try.

BranchChart.java
read along
1import javafx.application.Application;
2import javafx.scene.Scene;
3import javafx.scene.chart.PieChart;
4import javafx.stage.Stage;
5
6public class BranchChart extends Application {
7 @Override
8 public void start(Stage stage) {
9 PieChart chart = new PieChart();
10 chart.setTitle("Students per branch");
11 chart.getData().addAll(
12 new PieChart.Data("CSE", 60),
13 new PieChart.Data("IT", 30),
14 new PieChart.Data("ECE", 30));
15
16 stage.setTitle("PieChart");
17 stage.setScene(new Scene(chart, 500, 400));
18 stage.show();
19 }
20
21 public static void main(String[] args) {
22 launch(args);
23 }
24}
Output
A 500 by 400 window. The title "Students per branch" runs across the
top. Below it, a circle is cut into three coloured slices: CSE takes
half the circle, IT and ECE a quarter each. Each slice is labelled
with its name, and a legend underneath matches colours to names.

The values are relative. The chart adds them up (120) and gives each slice its share of the circle, so CSE's 60 becomes half the pie. Because a Chart is a Region, the chart can be the scene's root, as here, or sit inside any pane, and it redraws itself at the new size when the window is resized. PieChart.Data is a class nested inside PieChart, the same arrangement as Alert.AlertType later in this lesson.

WebView: a browser inside the scene#

A WebView shows a web page inside your window. It is a Parent but not a Region, which is why the hierarchy draws it beside Group and Region rather than under either.

Browser.java (inside start)
read along
1WebView browser = new WebView();
2browser.getEngine().load("https://openjfx.io");
3
4stage.setTitle("Browser");
5stage.setScene(new Scene(browser, 900, 600));
6stage.show();
Output
A 900 by 600 window showing the openjfx.io home page, given an
internet connection. Links work, and the page reflows when the
window is resized.

The work is split in two. getEngine() returns a WebEngine, which is not a node: it loads the page and runs its JavaScript. The WebView is the node that shows what the engine produces. The engine underneath is WebKit, the WebView box in the architecture diagram of the JavaFX overview. It lives in its own module, so both commands need it added: --add-modules javafx.controls,javafx.web.

The controls the programs use#

From the registration form to the factorial program, the JavaFX programs in this course put just seven kinds of thing on screen: Label, Text, TextField, PasswordField, TextArea, Button and Alert, with Font to size the text. All of them are in javafx.scene.control, except Text, Font and FontWeight, which are in javafx.scene.text.

Label, Text and Font#

LabelsAndText.java (inside start)
read along
1// Label: a control that shows text the user cannot edit
2Label nameLabel = new Label("Full Name : ");
3nameLabel.setText("Name : "); // change it later
4String shown = nameLabel.getText(); // "Name : "
5
6// Text: a shape whose outline happens to be letters
7Text text = new Text("Welcome to Ooty");
8text.setFont(new Font(45)); // size 45, default font family
9text.setX(50); // where the text starts
10text.setY(150); // the baseline the letters sit on
11
12// The registration form's heading: bold Arial, size 24
13Label headerLabel = new Label("Registration Form");
14headerLabel.setFont(Font.font("Arial", FontWeight.BOLD, 24));
Output
Once these are in a scene: nameLabel reads "Name : " in the normal
font. The Text draws "Welcome to Ooty" in large letters, starting 50
pixels from the left with its baseline 150 pixels down (when its
parent is a Group). The heading is bold Arial at size 24.
  • new Font(45)uses the constructor that takes only a size, so the text keeps the system's default font family. DisplayingText used exactly this.
  • Font.font("Arial", FontWeight.BOLD, 24) is a static factory method. The plain constructors have no weight parameter, so this is how you ask for bold: family Arial, weight bold, size 24. If Arial is not installed, JavaFX picks the closest font it has instead of failing.
  • FontWeight is an enum with nine weights, from THIN to BLACK. NORMAL and BOLD are the two you will use.
Text
Label
A Shape, in javafx.scene.text. Always a leaf.
A Control, in javafx.scene.control, and so a Region.
Placed by setX and setY, the start of its baseline. A layout pane overrides both, because the pane does the placing.
Placed by its pane, like every control.
As big as its letters. It wraps only if you call setWrappingWidth.
Resizable. Squeezed narrower than its text, it shows the start of the text followed by “...”.
Fill and stroke colours, like any shape.
Padding, background and border from Region, and an optional picture (a graphic) beside the text.
Used by DisplayingText, inside a Group.
Used for every caption in the form and the calculators.

The rule of thumb follows from the table: inside a layout pane, use a Label; to draw words at exact coordinates in a Group, use a Text.

TextField, PasswordField and TextArea#

TextInputs.java (inside start)
read along
1TextField nameField = new TextField();
2nameField.setPrefHeight(40); // taller than the default
3String name = nameField.getText(); // what the user typed, "" if nothing
4nameField.setText("Asha Verma"); // replace the contents
5
6TextField resultField = new TextField();
7resultField.setPromptText("Result"); // grey hint while the field is empty
8resultField.setEditable(false); // the program writes here, the user cannot
9
10PasswordField passwordField = new PasswordField();
11String password = passwordField.getText(); // the real characters, not the dots
12
13TextArea output = new TextArea();
14output.setText("Students:\n"); // many lines are allowed
15output.appendText("1 - John - 20\n"); // add to the end
Output
nameField is 40 pixels tall. resultField shows a grey "Result"
until the program writes into it, and ignores the keyboard.
passwordField shows one dot for each character typed.
output shows two lines: "Students:" and "1 - John - 20".
  • getText() always returns a String, and an empty one ("") when nothing has been typed. The registration form's isEmpty() checks rely on exactly that.
  • setPromptText("Result")shows a grey hint while the field is empty. It is not the field's text: getText() still returns "".
  • setEditable(false) turns a field into a display. The user can select and copy its text but not change it, while the program can still call setText. The calculators show their answers this way.
  • PasswordField extends TextField, so every method above works on it. It draws a dot for each character and refuses to copy or cut its contents to the clipboard, but getText() returns the real characters, because your program needs them.
  • TextArea holds many lines and shows scroll bars when the text outgrows it. StudentApp and EmployeeManagerApp use one, called output, to show query results and error messages. setWrapText(true) wraps long lines instead of scrolling sideways.

Button#

Buttons.java (inside start)
read along
1Button submitButton = new Button("Submit");
2submitButton.setPrefHeight(40);
3submitButton.setPrefWidth(100);
4submitButton.setDefaultButton(true); // Enter in this window presses it
5
6submitButton.setOnAction(e -> {
7 // runs every time the button is pressed
8 System.out.println("Submit pressed");
9});
Output
A 100 by 40 button labelled Submit, tinted blue because it is the
default button. Each click, and each press of Enter in that window,
prints "Submit pressed" in the console.
  • setOnAction(...) registers the code to run when the button fires: a click, or the Space key while the button has focus. The e -> { ... } is a lambda from enums and lambdas. The next lesson shows the longer form, an anonymous class, and explains both.
  • setDefaultButton(true) makes Enter press this button from anywhere in its window, unless the control with focus uses Enter itself (a TextArea uses it to start a new line). The default stylesheet tints the default button blue, which is why the Submit button on the registration form is blue.
  • setPrefWidth(100) and setPrefHeight(40) set its preferred size. A button does not stretch beyond its preferred size unless you raise its maximum with setMaxWidth(Double.MAX_VALUE).

Alert#

An Alert is a small ready-made dialog window, not a node: you never add it to a pane, you show it. The registration form builds every one of its messages like this.

Alerts.java (inside start)
read along
1Alert alert = new Alert(Alert.AlertType.ERROR);
2alert.setTitle("Form Error!"); // text in the dialog's title bar
3alert.setHeaderText(null); // no header band above the message
4alert.setContentText("Please enter your name"); // the message itself
5alert.initOwner(stage); // this dialog belongs to that window
6alert.show(); // appear, and carry on at once
Output
A small dialog titled "Form Error!" over the main window, with an
error icon beside the message "Please enter your name" and a
single OK button. Until it is closed, the main window ignores
clicks.
Alert.AlertTypeIcon and buttonsFits a message like
NONENo icon and no buttons. Add one yourself, because a dialog with no buttons cannot be closed by the user.A fully custom dialog
INFORMATIONInformation icon, OK“Record inserted!”
WARNINGWarning icon, OK“The table already exists.”
CONFIRMATIONQuestion icon, OK and Cancel“Delete John?”
ERRORError icon, OK“Please enter your name”
The five alert types. AlertType is an enum nested inside Alert, hence the dotted name.
  • setTitle sets the text in the dialog's title bar, and setContentText the message. Each type also fills in a default title and a default header text, which these calls replace.
  • setHeaderText(null) removes the header band that normally sits above the message, and the icon moves down beside the message instead.
  • initOwner(stage) ties the dialog to a window: it opens centred over that window and stays in front of it. It must be called before the dialog is shown.
  • show() puts the dialog on screen and returns immediately, so the next line of your code runs while the dialog is still up. showAndWait() returns only after the user closes the dialog, and hands back which button they pressed. Either way the user cannot click the main window while an alert is open, because alerts are modal. The difference is only whether your code waits.

That last difference matters for a question. To act on OK or Cancel, you need the answer, so you wait for it.

Asking a question
read along
1Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
2confirm.setHeaderText(null);
3confirm.setContentText("Delete John?");
4
5Optional<ButtonType> answer = confirm.showAndWait(); // waits here
6if (answer.isPresent() && answer.get() == ButtonType.OK) {
7 // delete the record
8}
Output
A dialog with a question icon, the text "Delete John?" and the
buttons OK and Cancel. The code after showAndWait() runs only once
the user has pressed one of them, and deletes only on OK.

Optional<ButtonType> is a small wrapper that may or may not hold a value (here, the button pressed); isPresent() checks that it does and get() takes it out. It lives in java.util, and ButtonType in javafx.scene.control.

Reading and writing each control#

ControlYour code writes it withYour code reads it with
LabelsetText(...), setFont(...)getText()
TextsetText(...), setFont(...), setX, setYgetText()
TextFieldsetText(...), clear(), setPromptText(...), setEditable(false)getText(), a String, empty if nothing was typed
PasswordFieldThe same as TextFieldgetText(), the real characters
TextAreasetText(...), appendText(...), clear()getText(), every line in one String with \n between lines
ButtonsetText(...), setDefaultButton(true), setPrefWidth(...)No value to read: setOnAction(...) runs your code when it is pressed
AlertsetTitle, setHeaderText, setContentText, initOwnershowAndWait() returns the button pressed
Everything a control knows comes out through a getter and every change goes in through a setter, encapsulation at work.

The layouts the programs use#

Two panes do all the arranging in these programs: a VBox in the two database screens and a GridPane everywhere else. Three small value classes tune them: Insets, Pos and HPos.

VBox and HBox#

StudentApp, the database program, builds its whole window in one line:

StudentApp.java (the layout line)
read along
1VBox root = new VBox(10, createBtn, insertBtn, updateBtn, deleteBtn, selectBtn, output);
2Scene scene = new Scene(root, 400, 300);
Output
A 400 by 300 window: five buttons stacked down the left edge, 10
pixels apart, and the output text area below them, stretched across
the full width of the window.
JavaFX JDBC Example
Create Table
Insert Record
Update Record
Delete Record
Select Records
output (TextArea)
No padding, so everything touches the left edge. The buttons keep their natural widths; the TextArea is stretched to the full width.

The constructor VBox(double spacing, Node... children) takes the gap first and then any number of children (the ... makes it a varargs parameter). Each child goes below the one before, 10 pixels apart. A VBox stretches resizable children to its own width when it can, so the TextAreafills the window's width, while the buttons keep their natural widths because a button's maximum width is its preferred width.

Spacing and padding are different settings, and confusing them is why layouts look almost right. Spacing is the gap betweenneighbouring children, so it appears only between them. Padding is the gap between the pane's edge and its children, so it appears only around the outside. StudentApp has spacing 10 and no padding, which is why its buttons touch the window edge. One more line would fix that:

Spacing and padding
read along
1VBox root = new VBox(10, createBtn, insertBtn, updateBtn, deleteBtn, selectBtn, output);
2root.setPadding(new Insets(10)); // 10 pixels inside each of the four edges
Output
The same window with a 10 pixel margin of empty space inside every
edge. The 10 pixel gaps between the buttons are unchanged.

HBox is the same idea turned sideways: new HBox(10, saveButton, cancelButton) lines the buttons up left to right, 10 pixels apart, and setAlignment(Pos.CENTER_RIGHT) pushes the whole row to the right-hand side. To let one child take the spare width of the row, give it permission with HBox.setHgrow(searchField, Priority.ALWAYS). An empty new Region() given the same permission makes an invisible spacer that pushes everything after it to the far end of the row.

GridPane: placing nodes by column and row#

A GridPane places each child in a cell of an invisible table. Columns and rows are numbered from 0, and the grid has as many of each as its children use. There are two ways to place a child:

  • grid.add(node, columnIndex, rowIndex) puts the node in one cell.
  • grid.add(node, columnIndex, rowIndex, columnSpan, rowSpan) lets it cover several: columnSpan cells across and rowSpan cells down, starting from the given cell.
SmallForm.java (inside start)
read along
1GridPane grid = new GridPane();
2grid.setHgap(10); // 10 pixels between columns
3grid.setVgap(10); // 10 pixels between rows
4grid.setPadding(new Insets(20)); // 20 pixels inside every edge
5
6TextField nameField = new TextField();
7TextField emailField = new TextField();
8Button saveButton = new Button("Save");
9
10grid.add(new Label("Name:"), 0, 0); // column 0, row 0
11grid.add(nameField, 1, 0); // column 1, row 0
12grid.add(new Label("Email:"), 0, 1); // column 0, row 1
13grid.add(emailField, 1, 1); // column 1, row 1
14grid.add(saveButton, 0, 2, 2, 1); // column 0, row 2, 2 columns wide, 1 row tall
15GridPane.setHalignment(saveButton, HPos.CENTER);
Output
Two rows, each a label with a field beside it: the labels line up in
one column and the fields in the next. A Save button sits centred
underneath, across both columns.
column 0labelscolumn 1fieldsrow 0row 1row 22 colsName:Email:Save
The small form's grid. Save starts in column 0 and spans both columns, and setHalignment centres it across them. The dashed lines are for reading only: a real GridPane draws no lines.
Column first, then row
grid.add(node, 1, 2) means column 1, row 2. That is the opposite of a two-dimensional array from two-dimensional arrays, where grid[1][2] means row 1, column 2. Think of x before y on a graph: the first number moves you across, the second moves you down. Mix them up and the form comes out transposed with no error message, because both orders are legal calls.

The console program below models the difference with an ordinary array. Its add method takes the column first, the way GridPane.add does, and stores into grid[row][column], the way an array is indexed. Step through it and watch which cell each call fills.

GridModel.java
1public class Main {
2 // A console model of GridPane.add(node, columnIndex, rowIndex).
3 // The method takes the column first. The array is indexed row first.
4 static void add(String[][] grid, String node, int column, int row) {
5 grid[row][column] = node;
6 }
7
8 public static void main(String[] args) {
9 String[][] grid = {
10 {".", "."}, // row 0
11 {".", "."}, // row 1
12 {".", "."} // row 2
13 };
14
15 add(grid, "Name:", 0, 0); // column 0, row 0
16 add(grid, "[name]", 1, 0); // column 1, row 0
17 add(grid, "Email:", 0, 1); // column 0, row 1
18 add(grid, "[email]", 1, 1); // column 1, row 1
19 add(grid, "[Save]", 1, 2); // column 1, row 2
20
21 for (int r = 0; r < grid.length; r++) {
22 System.out.println("row " + r + ": " + grid[r][0] + " " + grid[r][1]);
23 }
24 }
25}
Prints
›row 0: Name: [name]
›row 1: Email: [email]
›row 2: . [Save]

It prints row 0: Name: [name], then row 1: Email: [email], then row 2: . [Save]. Now change the third call to add(grid, "Email:", 1, 0), the classic slip, and run it again: the label lands in row 0, column 1, and replaces [name]. In a real GridPane nothing is replaced. The two nodes share the cell and are drawn on top of each other, which looks just as broken.

The calculators' way: setConstraints, then addAll#

SimpleCalculator and PerfectNumberChecker place their nodes in two steps instead of one. First a static method stamps a cell on each node; then a single call adds them all.

PerfectNumberChecker.java (the layout lines)
read along
1GridPane grid = new GridPane();
2grid.setPadding(new Insets(10, 10, 10, 10));
3grid.setVgap(8);
4grid.setHgap(10);
5
6GridPane.setConstraints(inputLabel, 0, 0); // column 0, row 0
7GridPane.setConstraints(inputField, 1, 0); // column 1, row 0
8GridPane.setConstraints(checkButton, 1, 1); // column 1, row 1
9GridPane.setConstraints(resultField, 1, 2); // column 1, row 2
10
11grid.getChildren().addAll(inputLabel, inputField, checkButton, resultField);
Output
"Enter an integer:" with a field beside it, the Check button under
the field, and the result field under that, all in the top-left
corner of a 300 by 200 window.

GridPane.setConstraints(node, column, row) takes the column first, like add. It adds nothing: it stores the column and row on the node itself, and the grid reads them once the node is one of its children. That is all add does internally too (store the position, then add the node to getChildren()), so the two styles build identical windows. The two-step style has one extra way to fail. A node added without its setConstraints call keeps the default position, column 0 and row 0, and lands on top of whatever is already there.

The other calls in that listing appear in almost every grid you will write.

  • setHgap(10) is the horizontal gap between neighbouring columns, and setVgap(8) the vertical gap between rows. Gaps go only between cells, never around the outside.
  • setPadding(new Insets(10, 10, 10, 10))is the empty space between the grid's edge and its cells. Insets is explained below.
  • setAlignment(Pos.CENTER), which this grid does not call, decides where the block of cells sits when the grid is bigger than its content. The default is Pos.TOP_LEFT, which is why the calculators hug the top-left corner, while the registration form, which sets Pos.CENTER, sits in the middle of its window.

A grid that needs more room than the window gives it is cut off at the edge. SimpleCalculator stacks nine rows in a 400 by 300 scene, and its result field ends up clipped at the bottom: the rows and their gaps need a little more than 300 pixels, and a GridPane has no scroll bar.

Column constraints, alignment and margins#

So far each column has been as wide as its widest child, and every child has sat at the left of its cell. The registration form changes both with one ColumnConstraints object per column.

ColumnConstraints (the registration form's two columns)
read along
1ColumnConstraints labels = new ColumnConstraints(100, 100, Double.MAX_VALUE);
2labels.setHalignment(HPos.RIGHT); // every node in column 0 sits at the right of its cell
3
4ColumnConstraints fields = new ColumnConstraints(200, 200, Double.MAX_VALUE);
5fields.setHgrow(Priority.ALWAYS); // column 1 takes any spare width
6
7grid.getColumnConstraints().addAll(labels, fields); // first for column 0, second for column 1
Output
Column 0 is 100 pixels wide, its labels pushed against the right
side of the column, next to the fields. Column 1 is at least 200
pixels wide and takes all the extra width as the window grows.
  • The constructor takes three widths: minimum, preferred and maximum. The column is laid out at its preferred width and never narrower than its minimum.
  • Double.MAX_VALUE is the largest number a doublecan hold, about 1.8 times 10 to the power 308. As a maximum width it means “no limit”. It allows a column to grow, but it does not make it grow.
  • setHgrow(Priority.ALWAYS) is what makes it grow: when the grid is wider than its columns need, the spare width goes to this column. Priority has three values: ALWAYS, SOMETIMES (grow only if no ALWAYS column wants the space) and NEVER.
  • setHalignment(HPos.RIGHT) sets where every node in the column sits across its cell. The default is HPos.LEFT.
  • getColumnConstraints()is the grid's list of constraints, matched to columns by position: the first object governs column 0, the second column 1.

Two more static methods adjust a single node rather than a whole column.

One node at a time
read along
1GridPane.setHalignment(saveButton, HPos.CENTER); // this node only; beats its column's setting
2GridPane.setMargin(headerLabel, new Insets(20, 0, 20, 0)); // 20 pixels above and below this node
Output
The button is centred across the cells it spans. The heading gets
20 pixels of space above it and 20 below it, inside its row.

Notice which calls start with the class name. grid.add(...)is an instance method: it changes this grid's list of children. GridPane.setConstraints, GridPane.setHalignment and GridPane.setMargin are static. They belong to no particular grid; they only store a value on the node, and whichever GridPane holds the node reads that value when it lays out. That is the static idea again, in a library you did not write.

Insets, Pos and HPos#

All three live in javafx.geometry. They are small values that other calls take as arguments.

  • Insets(top, right, bottom, left) holds four distances in the order a clock hand meets them, starting at twelve: top, right, bottom, left. new Insets(20) gives all four sides the same value. You pass an Insets to setPadding and to GridPane.setMargin.
  • Pos combines a vertical and a horizontal position in one value: TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT, CENTER, CENTER_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, and three BASELINE_ values that line up the bottoms of letters. A pane's setAlignment takes one.
  • HPos is horizontal only: LEFT, CENTER, RIGHT. It is used for a whole column (ColumnConstraints.setHalignment) or for one node in a grid (GridPane.setHalignment). Its vertical partner, VPos, has TOP, CENTER, BASELINE and BOTTOM.

Pos, HPos and VPos are enums, the fixed sets of named values from enums and lambdas. Every Insets in these programs is one of these four:

WrittenTopRightBottomLeftWhere
new Insets(40, 40, 40, 40)40404040The registration form's padding
new Insets(20)20202020InterestCalculator and ArmstrongFactorial
new Insets(10, 10, 10, 10)10101010SimpleCalculator and PerfectNumberChecker
new Insets(20, 0, 20, 0)200200The margin round the form's heading and its Submit button

BorderPane, briefly#

A BorderPane has five named regions, filled with setTop, setBottom, setLeft, setRight and setCenter. Top and bottom stretch across the full width, left and right take their preferred width, and the centre gets everything that is left, growing and shrinking with the window. A region you never set takes no space at all.

Shell.java (inside start)
read along
1Button insertBtn = new Button("Insert Record");
2Button selectBtn = new Button("Select Records");
3VBox buttons = new VBox(10, insertBtn, selectBtn);
4TextArea output = new TextArea();
5
6BorderPane root = new BorderPane();
7root.setTop(new Label("Student records"));
8root.setLeft(buttons);
9root.setCenter(output);
10root.setBottom(new Label("Connected to studentdb"));
Output
A heading across the top, two buttons down the left, the text area
filling the middle and growing with the window, and a status line
along the bottom. No right region was set, so it takes no space.
BorderPane
setTop: Label
setLeft: VBox
setCenter: TextArea (grows)
setBottom: Label
The five regions of a BorderPane, with the right one left empty. The centre takes all the remaining space.
Next: all of this in one program
The next lesson builds a registration form, which uses a GridPane root, both ColumnConstraints, a Label heading in bold Arial, two text fields, a password field, a default button and two kinds of Alert. Every line of it is now familiar. Continue with form creation using JavaFX.

Try it

Answer each one in your head or on paper first, then open the answer.

1
A method has the signature void addTo(Pane parent, Node child). Can you pass a Button as the child? A VBox as the child? A Label as the parent? Explain each answer from the class hierarchy.
2
Which of these compile as the first argument of new Scene(..., 400, 300): a Group, a GridPane, a Button, a Text, an ImageView, a WebView? Say why for each.
3
A Group root holds one Text with setX(500) and setY(100), in a scene 400 pixels wide. What do you see? What changes when the user drags the window to 600 wide? And what if the root were a StackPane instead?
4
Someone wants the Email label and its field in row 1, label on the left, and writes grid.add(new Label("Email:"), 1, 0); and grid.add(emailField, 0, 1);. Where does each node end up, and what is the fix?
5
In VBox root = new VBox(10, a, b, c); followed by root.setPadding(new Insets(5, 10, 15, 20));, how far is a from the top edge, how far is b below a, and how far is c from the left edge?
6
You want to ask “Delete John?” and delete the record only if the user presses OK. Which AlertType do you use, and why does show() not work here?

Practice

Work through these on your own. Each one builds on this page.

1
Build PerfectNumberChecker's layout twice: once with GridPane.setConstraints and getChildren().addAll, once with grid.add. Confirm the two windows are identical. Then delete the setConstraints line for the Check button and describe exactly what you see, and why.
2
Rebuild StudentApp's window with a BorderPane root: the five buttons in a FlowPane along the top, and the TextArea in the centre. Drag the window narrower and wider, and write a paragraph comparing it with the VBox version.
3
Write a program whose root is a PieChart of how you spend a typical day, in five slices that add up to 24 hours. Then show the same five numbers in a BarChart with a CategoryAxis and a NumberAxis, and say what each chart makes easier to see.
4
Take GroupRoot and run it three ways: with the Group root, with a StackPane root, and with the button itself as the root (new Scene(button, 300, 200)). Before each run, write down what you expect to see when the window is resized. Afterwards, explain every result from the Node hierarchy.

Spotted a mistake, or a part that did not make sense? Report it by email