Chapter 4

Using Layout Panes to Arrange Your Scenes

IN THIS CHAPTER

check Using five popular layout pane classes: HBox, VBox, FlowPane, BorderPane, and GridPane .

check Adjusting the size of layout panes and the nodes they contain

check Fiddling with various options for spacing out the nodes in a layout pane

Controlling the layout of components in a scene is often one of the most difficult aspects of working with JavaFX. In fact, at times it can be downright exasperating. Often the components almost seem to have minds of their own. They get stubborn and refuse to budge. They line up on top of one another when you want them to be side by side. You make a slight change to a label or text field, and the whole scene seems to rearrange itself. At times, you want to put your fist through the monitor.

warning I recommend against putting your fist through your monitor. You’ll make a mess, cut your hand, and have to spend money on a new monitor — and when you get your computer working again, the components still won’t line up the way you want them to be.

The problem isn’t with the components; it’s with the layout panes, which determine where each component appears in its frame or panel. Layout panes are special classes whose sole purpose in life is to control the arrangement of the nodes that appear in a scene. JavaFX provides several distinct types of layout panes; each type uses a different approach to controlling the arrangement of nodes. The trick to successfully lay out a scene is to use the layout panes in the correct combination to achieve the arrangement you want.

Working with Layout Panes

Understanding layout panes is the key to creating JavaFX frames that are attractive and usable.

Introducing five JavaFX layout panes

JavaFX provides many different layout panes for you to work with. I explain the following five in this chapter:

To give you a general idea of the results that can be achieved with the first four layout types, Figure 4-1 shows four sample windows that each use one of the layout panes. You’ll see a detailed example of how the GridPane layout works later in this chapter.

image

FIGURE 4-1: Four commonly used types of layout panes.

Creating layout panes

The basic process of working with layout panes is simple. Here is the general procedure for creating a layout node:

  1. Create the controls or other nodes you want to add to the pane.

    For example, if the layout pane will contain two buttons, you should create the two buttons using code similar to this:

    Button btnOK = new Button();
    btnOK.setText("OK");
    btnOK.setOnAction( e -> btnOK_Click() );
    Button btnCancel = new Button();
    btnCancel.setText("Cancel");
    btnCancel.setOnAction( e -> btnCancel_Click() );

  2. Create a layout pane by calling its constructor.

    For example:

    HBox pane = new HBox();

  3. Fine-tune the optional settings used by the layout pane.

    Each type of layout pane has a unique assortment of optional parameters that govern the details of how nodes are laid out within the pane. For example, the HBox pane lets you set the number of pixels that will be used to separate each node in the pane. You can set this value as follows:

    HBox.setSpacing(10);

  4. Add each of the nodes that will appear in the layout pane.

    Each type of layout pane provides a method for adding nodes to the pane. For the HBox pane, you must first call the getChildren method to get a list of all the nodes that have been added to the pane. Then, you call the addAll method to add one or more nodes to the pane. For example:

    pane.getChildren().addAll(btnOK, btnCancel);

  5. Create the scene, specifying the layout pane as the scene’s root node.

    For example:

    Scene scene = new Scene(pane, 300, 400);

    In this example, pane is added as the root node for the scene.

Combining layout panes

You can combine several layout panes to create layouts that are more complicated than a single layout pane can provide. For example, suppose you want to create a layout that has a horizontal row of buttons at the bottom and a vertical column of buttons at the right. To do that, you could create an HBox for the buttons at the bottom and a VBox for the buttons at the right. Then, you could create a BorderPane and add the HBox to the bottom region and the VBox to the right region.

Combinations like this are possible because all the layout panes inherit the base class javafx.scene.layout.Pane , which in turn inherits the class javafx.scene.node . In other words, all panes are also nodes. Each node that you add to a layout pane can be another layout pane. You can nest layout panes within layout panes as deeply as you need to achieve the exact layout you need for your application.

Using the HBox Layout

The HBox class provides one of the simplest of all JavaFX’s layout managers: It arranges one or more nodes into a horizontal row. Table 4-1 presents the most commonly used constructors and methods of the HBox class.

TABLE 4-1 HBox Constructors and Methods

Constructor

Description

HBox()

Creates an empty HBox .

HBox(double spacing)

Creates an empty HBox with the specified spacing.

HBox(Node… children)

Creates an HBox with the specified child nodes. This constructor lets you create an HBox and add child nodes to it at the same time.

HBox(double spacing, Node… children)

Creates an HBox with the specified spacing and child nodes.

Method

Description

ObservableList<Node> getChildren()

Returns the collection of all child nodes that have been added to the HBox . The collection is returned as an ObservableList type, which includes the method addAll , letting you add one or more nodes to the list.

static void setAlignment(Pos alignment)

Sets the alignment for child nodes within the HBox .

See Table 4-5 for an explanation of the Pos enumeration. For more information, see the section “Aligning Nodes in a Layout Pane ” later in this chapter.

static void setHgrow(Node child, Priority priority)

Sets the growth behavior of the given child node.

See Table 4-3 for an explanation of the Priority enumeration. For more information, see the section “Adding Space by Growing Nodes ” later in this chapter.

static void setMargin(Node child, Insets value)

Sets the margins for a given child node. See Table 4-2 for the constructors of the Insets class.

For more information, see the section “Adding Space with Margins ” later in this chapter.

void setPadding(Insets value)

Sets the padding around the inside edges of the Hbox .

See Table 4-2 for the constructors of the Insets class.

For more information, see the section “Spacing Things Out ” later in this chapter.

void setSpacing(double value)

Sets the spacing between nodes displayed within the HBox .

For more information, see the section “Spacing Things Out ” later in this chapter.

The HBox class is defined in the javafx.scene.layout package, so you should include the following import statement in any program that uses an HBox :

import javafx.scene.layout.*;

The easiest way to create an HBox is to first create the nodes that you want to place in the HBox and then call the HBox constructor and pass the nodes as arguments. For example:

Button btn1 = new Button("Button One");
Button btn2 = new Button("Button Two");
Button btn3 = new Button("Button Three");
HBox hbox = new HBox(btn1, btn2, btn3);

If you prefer to create the HBox control in an initially empty state and later add the controls, you can do so like this:

HBox hbox = new HBox();
Hbox.getChildren().addAll(btn1, btn2, btn3);

Here, the getChildren method is called, which returns a collection of all the children added to the HBox pane. This collection is defined by the class ObservableList , which includes a method named addAll that you can use to add one or more nodes to the list.

Spacing Things Out

By default, child nodes in a layout pane are arranged immediately next to one another, with no empty space in between. If you want to provide space between the nodes in the pane, you can do so in four ways:

In this section, I show you how to add spacing and padding to a pane. Then, the next three sections show you how to use the other two techniques.

Note that although I illustrate the techniques in these sections using the HBox layout pane, the techniques apply to other types of panes as well.

To set the spacing for an HBox pane, you can use the spacing parameter on the HBox constructor or by calling the setSpacing method. For example, this statement creates an HBox pane with a default spacing of 10 pixels:

HBox hbox = new HBox(10);

This example creates an HBox pane with 10-pixel spacing and adds three buttons:

HBox hbox = new HBox(10, btn1, btn2, btn3);

And this example creates an HBox pane using the default constructor, and then calls the setSpacing method to set the spacing to 10 pixels:

HBox hbox = new HBox();
Hbox.setSpacing(10);

Although spacing adds space between nodes in an HBox pane, it doesn’t provide any space between the nodes and the edges of the pane itself. For example, if you set the spacing to 10 pixels and add three buttons to the pane, the three buttons will be separated from one another by a gap of 10 pixels. However, there won’t be any space at all between the left edge of the first button and the left edge of the pane itself. Nor will there be any space between the top of the buttons and the top of the pane. In other words, the three buttons will be crowded tightly into the pane.

To add space around the perimeter of the layout pane, use the setPadding method. This method takes as a parameter an object of type Insets , which represents the size of the padding (in pixels) for the top, right, bottom, and left edge of an object. You can create an Insets object using either of the two constructors listed in Table 4-2 . The first provides an even padding for all four edges of an object; the second lets you set a different padding value for each edge.

To set the padding to a uniform 10 pixels, call the setPadding method like this:

hbox.setPadding(new Insets(10));

To set a different padding value for each edge, call it like this:

hbox.setPadding(new Insets(20, 10, 20, 10));

In this example, the top and bottom padding is set to 20 and the right and left padding is set to 10.

TABLE 4-2 Insets Constructors

Constructor

Description

Insets(double value)

Creates an Insets object that uses the same value for the top, right, bottom, and left margins.

Insets(double top, double right, double bottom, double left)

Creates an Insets object that uses the specified top, right, bottom, and left margins.

The Insets enumeration is defined in the javafx.geometry package, so you should include the following import statement in any program that uses Insets :

import javafx.geometry.*;

Adding Space with Margins

Another way to add space around the nodes in a layout pane is to create margins around the individual nodes. This technique allows you to set a different margin size for each node in the layout pane, giving you complete control over the spacing of each node.

To create a margin, call the setMargin method for each node you want to add a margin to. You might think that because each node can have its own margin, the setMargin method would belong to the Node class. Instead, the setMargin method is defined by the HBox class. The setMargin method accepts two parameters:

Here’s an example that sets a margin of 10 pixels for all sides of a button named btn1 :

HBox hbox = new HBox();
hbox.setMargin(btn1, new Insets(10));

The setMargin method is a static method of the HBox class, so when you call it, you can reference the HBox class itself rather than an actual instance of an HBox . Thus, the following code will work equally as well:

Hbox.setMargin(btn1, new Insets(10));

(Yes it’s a subtle difference: In the first example, hbox refers to an instance of the HBox class; in the second example, HBox refers to the class itself.)

Here’s an example that sets a different margin for each side of the pane:

Hbox.setMargin(btn1, new Insets(10, 15, 20, 10));

In this example, the top margin is 10 pixels, the right margin is 15 pixels, the bottom margin is 20 pixels, and the left margin is 10 pixels.

Note that margins, spacing, and padding can work together. Thus, if you create a 5-pixel margin on all sides of two buttons, add those two buttons to a pane whose spacing is set to 10 pixels and whose padding is set to 10 pixels, the buttons will be separated from one another by a space of 20 pixels and from the inside edges of the pane by 15 pixels.

Adding Space by Growing Nodes

A third way to add space between nodes in an HBox is to create a node whose sole purpose is to add space between two HBox nodes. Then, you can configure the spacer node that will automatically grow to fill any extra space within the pane. By configuring only the spacer node and no other nodes in this way, only the spacer node will grow. This has the effect of pushing the nodes on either side of the spacer node apart from one another.

For example, suppose you want to create an HBox layout pane that contains three buttons. Instead of spacing all three buttons evenly within the pane, you want the first two buttons to appear on the left side of the pane and the third button to appear on the right side of the pane. The amount of space between the second and third buttons will depend entirely on the size of the pane. Thus, if the user drags the window to expand the stage, the amount of space between the second and third buttons should increase accordingly.

The easiest way to create a spacer node is by using the Region class. The Region class is the base class for both the Control class, from which controls such as Button and Label derive. It is also the based class for the Pane class, from which all the layout panes described in this chapter derive.

For my purposes here, I just use the simple default constructor of the Region class to create a node that serves as a simple spacer in a layout pane. I don’t provide a specific size for the region. Instead, I configure it so that it will grow horizontally to fill any unused space within its container.

To do that, you use the static setHgrow method of the HBox class, specifying one of the three constant values defined by an enumeration named Priority enumeration. Table 4-3 lists these constants and explains what each one does.

TABLE 4-3 The Priority enumeration

Constant

Description

Priority.NEVER

Indicates that the width of the node should never be adjusted to fill the available space in the pane. This is the default setting. Thus, by default, nodes are not resized based on the size of the layout pane that contains them.

Priority.ALWAYS

Indicates that the width of the node should always be adjusted if necessary to fill available space in the pane. If you set two or more nodes to ALWAYS , the adjustment will be split equally among each of the nodes.

Priority.SOMETIMES

Indicates that the node’s width may be adjusted if necessary to fill out the pane. However, the adjustment will be made only if there are no other nodes that specify ALWAYS .

The Priority enumeration is defined in the javafx.scene.layout package; the same package that defines the layout managers that require it. So you don’t need to include an additional import statement to use the Priority enumeration.

The following example creates three buttons and a spacer, set the margins for all three buttons to 10 pixels, and then add the three buttons and the spacer to an HBox such that the first two buttons appear on the left of the HBox and the third button appears on the right:

// Create the buttons
Button btn1 = new Button("One");
Button btn2 = new Button("Two");
Button btn3 = new Button("Three");

// Create the spacer
Region spacer = new Region();

// Set the margins
hBox.setMargin(btn1, new Insets(10));
hBox.setMargin(btn2, new Insets(10));
hBox.setMargin(btn3, new Insets(10));

// Set the Hgrow for the spacer
hBox.setHgrow(spacer, Priority.ALWAYS);

// Create the HBox layout pane
HBox hbox = new HBox(10, btn1, btn2, spacer, btn3);

Figure 4-2 shows how this pane appears when added to a stage. So that you can see how the spacer works, the first shows three incarnations of the pane, each with the window dragged to a different size. Notice how the spacing between the second and third buttons is adjusted automatically so that the first two buttons are on the left side of the pane and the third button is on the right.

image

FIGURE 4-2: Using a spacer node to space out buttons in an HBox pane.

Like the setMargin method, the setHgrow method is a static class of the HBox class. Thus, you can call it from an instance of the HBox class (as in the preceding example), or you can call it from the HBox class itself. In other words, the second two lines in the following code segment are redundant:

HBox pane = new HBox();
pane.setHgrow(spacer, Priority.ALWAYS);
HBOX.setHgrow(spacer, Priority.ALWAYS);

Using the VBox Layout

The VBox class is similar to the HBox class, but instead of arranging nodes horizontally in a row, it arranges them vertically in a column. Table 4-4 shows the most commonly used constructors and methods of the VBox class.

TABLE 4-4 VBox Constructors and Methods

Constructor

Description

VBox()

Creates an empty VBox .

VBox(double spacing)

Creates an empty VBox with the specified spacing.

VBox(Node… children)

Creates an VBox with the specified child nodes. This constructor lets you create a VBox and add child nodes to it at the same time.

VBox(double spacing, Node… children)

Creates a VBox with the specified spacing and child nodes.

Method

Description

ObservableList<Node> getChildren()

Returns the collection of all child nodes that have been added to the VBox . The collection is returned as an ObservableList type, which includes the method addAll , letting you add one or more nodes to the list.

static void setAlignment(Pos alignment)

Sets the alignment for child nodes within the HBox .

See Table 4-5 for an explanation of the Pos enumeration. For more information, see the section “Aligning Nodes in a Layout Pane ” later in this chapter.

static void setMargin(Node child, Insets value)

Sets the margins for a given child node.

See Table 4-2 for the constructors of the Insets class. For more information, see the section “Adding Space with Margins ” earlier in this chapter.

void setPadding(Insets value)

Sets the padding around the inside edges of the VBox .

See Table 4-2 for the constructors of the Insets class. For more information, see the section “Spacing Things Out ” earlier in this chapter.

static void setVgrow(Node child, Priority priority)

Sets the growth behavior of the given child node.

See Table 4-3 for an explanation of the Priority enumeration. For more information, see the section “Adding Space by Growing Nodes ” earlier in this chapter.

The VBox class is defined in the javafx.scene.layout package, so you should include the following import statement in any program that uses an VBox :

import javafx.scene.layout.*;

Here’s an example that creates three buttons and uses a VBox to arrange them into a column:

Button btn1 = new Button("Button One");
Button btn2 = new Button("Button Two");
Button btn3 = new Button("Button Three");
VBox vbox = new VBox(btn1, btn2, btn3);

You can accomplish the same thing by using the default constructor and calling the getChildren method, as in this example:

VBox vbox = new VBox();
Vbox.getChildren().addAll(btn1, btn2, btn3);

As with the HBox class, you can use spacing, padding, margins, and spacer nodes to control the spacing of nodes within a VBox . Here’s an example that sets 10 pixels of vertical space between nodes and 10 pixels of padding on each edge of the pane:

Button btn1 = new Button("One");
Button btn2 = new Button("Two");
Button btn3 = new Button("Three");
VBox vbox = new VBox(10, btn1, btn2, btn3);
vbox.setPadding(new Insets(10));

Here’s an example that creates a column of three buttons, with one button at the top of the column and two at the bottom, with 10 pixels of spacing and padding:

// Create the buttons
Button btn1 = new Button("One");
Button btn2 = new Button("Two");
Button btn3 = new Button("Three");

// Create the spacer
Region spacer = new Region();

// Set the Vgrow for the spacer
VBox.setVgrow(spacer, Priority.ALWAYS);

// Create the VBox layout pane
VBox vbox = new VBox(10, btn1, spacer, btn2, btn3);
vbox.setPadding(new Insets(10));

Aligning Nodes in a Layout Pane

Both the HBox and the VBox layout panes have a setAlignment method that lets you control how the nodes that are contained within the pane are aligned with one another. The setAlignment method accepts a single argument, which is one of the constants defined by the Pos enumeration, described in Table 4-5 .

TABLE 4-5 The Pos enumeration

Constant

Vertical Alignment

Horizontal Alignment

Pos.TOP_LEFT

Top

Left

Pos.TOP_CENTER

Top

Center

Pos.TOP_RIGHT

Top

Right

Pos.CENTER_LEFT

Center

Left

Pos.CENTER

Center

Center

Pos.CENTER_RIGHT

Center

Right

Pos.BOTTOM_LEFT

Bottom

Left

Pos.BOTTOM_CENTER

Bottom

Center

Pos.BOTTOM_RIGHT

Bottom

Right

Pos.BASELINE_LEFT

Baseline

Left

Pos.BASELINE_CENTER

Baseline

Center

Pos.BASELINE_RIGHT

Baseline

Right

The Pos enumeration is defined in the javafx.geometry package, so you should include the following import statement in any program that uses Insets :

import javafx.geometry.*;

The following example shows how you might create a vertical column of three buttons, centered within the pane:

Button btn1 = new Button("Number One");
Button btn2 = new Button("Two");
Button btn3 = new Button("The Third Button");
VBox vbox = new VBox(10, btn1, btn2, btn3);
vbox.setPadding(new Insets(10));
vbox.setAlignment(Pos.CENTERED);

When this pane is added to a scene and then shown in a stage, the results resemble the window shown in Figure 4-3 .

image

FIGURE 4-3: Three buttons centered in a VBox layout pane.

Making Nodes the Same Width

When you place a set of buttons or other controls in a layout pane, you may want the buttons to all have the same width to create a neat, even appearance. This is especially true when you place them in a vertical column in a VBox pane because the vertical column will draw attention to any differences in the widths of the buttons.

You can easily dictate that the buttons all have the same width by setting the maximum width of each of the buttons to Double.MAX_VALUE . Here’s a revised version of the preceding example in which the three buttons are set to the same width:

Button btn1 = new Button("Number One");
Button btn2 = new Button("Two");
Button btn3 = new Button("The Third Button");
btn1.setMaxWidth(Double.MAX_VALUE);
btn2.setMaxWidth(Double.MAX_VALUE);
btn3.setMaxWidth(Double.MAX_VALUE);
VBox vbox = new VBox(10, btn1, btn2, btn3);
vbox.setPadding(new Insets(10));
vbox.setAlignment(Pos.CENTERED);

Figure 4-4 shows how these buttons appear when the pane is added to a scene and the scene displayed in a stage. Notice that all three buttons have adopted the width of the widest button (btn3 ).

image

FIGURE 4-4: Three buttons with the same width.

Using the Flow Layout

The flow layout comes in two flavors: horizontal and vertical. A horizontal flow layout arranges its child nodes in a row until the width of the pane reaches a certain size that you can specify. When that size is reached, the layout begins a new row of child nodes beneath the first row. This flow continues, starting a new row each time the size limit is reached, until all the child nodes have been placed.

A vertical flow layout works the same way except that child nodes are laid out in columns until the size limit is reached. When the size limit is reached, a new column immediately to the right of the first column is started.

You use the FlowPane class to create a flow layout. Table 4-6 shows the constructors and most commonly used methods for the FlowPane class.

TABLE 4-6 FlowPane Constructors and Methods

Constructor

Description

FlowPane()

Creates an empty horizontal flow layout with both the horizontal and vertical gaps set to zero.

FlowPane(double hgap, double vgap)

Creates an empty horizontal flow layout with the specified horizontal and vertical gaps.

FlowPane(double hgap, double vgap, Node… children)

Creates a horizontal flow layout with the specified horizontal and vertical gaps and populated with the specified child nodes.

FlowPane(Node… children)

Creates a horizontal flow layout with both the horizontal and vertical gaps set to zero and populated with the specified child nodes.

Note: In each of the following constructors, Orientation can be Orientation.HORIZONTAL or Orientation.VERTICAL .

FlowPane(Orientation orientation)

Creates an empty flow layout with the specified orientation and both the horizontal and vertical gaps set to zero.

FlowPane(Orientation orientation, double hgap, double vgap)

Creates an empty flow layout with the specified orientation and the specified horizontal and vertical gaps.

FlowPane(Orientation orientation, double hgap, double vgap, Node… children)

Creates a flow layout with the specified orientation and horizontal and vertical gaps, populated with the specified children.

FlowPane(Orientation orientation, Node… children)

Creates a flow layout with the specified orientation and both the horizontal and vertical gaps set to zero, populated with the specified children.

Method

Description

ObservableList<Node> getChildren()

Returns the collection of all child nodes. The collection is returned as an ObservableList type, which includes the method addAll , letting you add one or more nodes to the list.

void setAlignment(Pos alignment)

Sets the alignment for nodes within the rows and columns.

See Table 4-5 for an explanation of the Pos enumeration. For more information, see the section “Aligning Nodes in a Layout Pane ” earlier in this chapter.

void setColumnAlignment(Pos alignment)

Sets the alignment for nodes within the columns.

See Table 4-5 for an explanation of the Pos enumeration. For more information, see the section “Aligning Nodes in a Layout Pane ” earlier in this chapter.

void setHgap(double value)

Sets the horizontal gap. For a horizontal flow layout, this is the amount of space between nodes. For a vertical flow layout, this is the amount of space between columns.

static void setMargin(Node child, Insets value)

Sets the margins for a given child node.

See Table 4-2 for the constructors of the Insets class. For more information, see the section “Adding Space with Margins ” later in this chapter.

void setOrientation(Orientation orientation)

Sets the orientation of the flow layout, which can be Orientation.HORIZONTAL or Orientation.VERTICAL.

void setPadding(Insets value)

Sets the padding around the inside edges of the flow layout .

See Table 4-2 for the constructors of the Insets class. For more information, see the section “Spacing Things Out ” earlier in this chapter.

void setPrefWrapLength(double value)

Sets the preferred wrap length for the pane. For a horizontal flow layout, this represents the preferred width of the pane; for a vertical flow layout, it represents the preferred height.

void setRowAlignment(Pos alignment)

Sets the alignment for nodes within the rows.

See Table 4-5 for an explanation of the Pos enumeration. For more information, see the section “Aligning Nodes in a Layout Pane ” earlier in this chapter.

void setSpacing(double value)

Sets the spacing between nodes displayed within the flow layout. For more information, see the section “Spacing Things Out ” earlier in this chapter.

void setVgap(double value)

Sets the vertical gap. For a vertical flow layout, this is the amount of space between nodes. For a horizontal flow layout, this is the amount of space between rows.

The FlowPane class is defined in the javafx.scene.layout package, so you should include the following import statement in any program that uses a flow layout:

import javafx.scene.layout.*;

The constructors for this class let you specify the horizontal and vertical gaps, which provide the spacing between the horizontal and vertical elements of the layout, the orientation (horizontal or vertical), and the child nodes with which to populate the layout.

To set the limit at which the flow layout wraps, you use the setPrefWrapLength method. The wrap length is applied to the dimension in which the pane flows its contents. Thus, for a horizontal flow layout, the wrap length specifies the preferred width of the pane; for a vertical flow layout, the wrap length specifies the pane’s preferred height.

Note that regardless of the preferred wrap length, if you don’t call this method, the wrap length defaults to 400 pixels.

The following example creates a horizontal layout with 10 pixels of horizontal and vertical gaps, populated by five buttons, and a preferred wrap length of 300 pixels:

Button btn1 = new Button("Button One");
Button btn2 = new Button("Button Two");
Button btn3 = new Button("Button Three");
Button btn4 = new Button("Button Four");
Button btn5 = new Button("Button Five");
FlowPane pane = new FlowPane(Orientation.HORIZONTAL,
10, 10, btn1, btn2, btn3, btn4, btn5);
pane.setPrefWrapLength(300);

Figure 4-5 shows how these buttons appear when the layout is added to a scene and the scene displayed in a stage. This figure also shows how the buttons in the flow layout are rearranged when the user resizes the window. Notice that initially, the first three buttons appear on the first row and the next two appear on the second row. When the window is dragged a bit wider, the buttons reflow so that four fit on the first row and just one spills to the second row. Then, when the window is dragged smaller, just two buttons appear on the first two rows and a third row is created for the fifth button.

image

FIGURE 4-5: A flow layout pane with five buttons.

Using the Border Layout

The border layout is a pane that is carved into five regions: Top, Left, Center, Right, and Bottom, as shown in Figure 4-6 . When you add a component to the layout, you can specify which of these regions the component goes in.

image

FIGURE 4-6: How the border layout carves things up.

tip Border layout is the ideal layout manager for applications that have a traditional window arrangement in which menus and toolbars are displayed at the top of the window, a status bar or OK and Cancel buttons are displayed at the bottom, a navigation pane is displayed on the left, various task panes are displayed on the right, and content is displayed in the middle.

You use the BorderPane class to create a border layout. Table 4-7 lists the constructors and the most commonly used methods for the BorderPane class.

TABLE 4-7 BorderPane Constructors and Methods

Constructor

Description

BorderPane ()

Creates an empty border layout.

BorderPane (Node center)

Creates a border layout with the specified center node.

BorderPane (Node center, Node top, Node right, Node bottom, Node left)

Creates a border layout with the specified center, top, right, bottom, and left nodes.

Method

Description

void setCenter(Node node)

Sets the center node.

void setTop(Node node)

Sets the top node.

void setRight(Node node)

Sets the right node.

void setBottom(Node node)

Sets the bottom node.

void setLeft(Node node)

Sets the left node.

void setAlignment(Pos alignment)

Sets the alignment for nodes within border pane.

See Table 4-5 for an explanation of the Pos enumeration. For more information, see the section “Aligning Nodes in a Layout Pane ” earlier in this chapter.

static void setMargin(Node child, Insets value)

Sets the margins for a given child node.

See Table 4-2 for the constructors of the Insets class. For more information, see the section “Adding Space with Margins ” earlier in this chapter.

The BorderPane class is defined in the javafx.scene.layout package, so you should include the following import statement in any program that uses a border layout:

import javafx.scene.layout.*;

The default constructor for this class creates an empty border layout, to which you can add nodes later, as in this example:

Button btn1 = new Button("Button One");
Button btn2 = new Button("Button Two");
Button btn3 = new Button("Button Three");
VBox vbox = new VBox(btn1, btn2, btn3);

BorderPane pane = new BorderPane();
pane.setCenter(vbox);

Here, three buttons are created and added to a VBox . Then, a border layout is created, and the VBox is added to its center region.

Alternatively, you can add a node to the center region via the BorderPane constructor, like this:

BorderPane pane = new BorderPane(vbox);

The third constructor listed in Table 4-7 lets you add nodes to all five regions at once. The following example assumes that you have already created five panes, named centerPane , topPane , rightPane , bottomPane , and leftPane :

BorderPane pane = new BorderPane(centerPane,
topPane, rightPane, bottomPane, leftPane);

tip Here are a few additional important points to know about the BorderPane class:

Using the GridPane Layout

The grid pane layout manager lets you arrange GUI elements in a grid of rows and columns. Unlike a tile pane, the rows and columns of a grid pane do not have to be the same size. Instead, the grid pane layout automatically adjusts the width of each column and the height of each row based on the components you add to the panel.

Here are some important features of the grid pane layout manager:

The following sections describe the ins and outs of working with grid pane layouts.

Sketching out a plan

Before you create a grid pane layout, draw a sketch showing how you want the components to appear in the panel. Then slice the panel into rows and columns, and number the rows and columns starting with zero in the top-left corner. Figure 4-7 shows such a sketch for an application that lets a user order a pizza.

image

FIGURE 4-7: Sketching out a panel.

After you have the panel sketched out, list the components, their x and y coordinates on the grid, their alignment, and whether each component spans more than one row or column. Here’s an example:

Component

x

y

Alignment

Spans

Label "Name"

0

0

Right

Label "Phone"

0

1

Right

Label "Address"

0

2

Right

Name text field

1

0

Left

2

Phone text field

1

1

Left

2

Address text field

1

2

Left

2

Size radio buttons

0

3

Left

Style radio buttons

1

3

Left

Toppings check boxes

2

3

Left

OK and Close buttons

2

4

Right

After you lay out the grid, you can write the code to put each component in its proper place.

Creating a grid pane

Table 4-8 shows the most frequently used constructors and methods of the GridPane class, which you use to create a grid pane.

TABLE 4-8 GridPane Constructors and Methods

Constructor

Description

GridPane()

Creates an empty grid pane.

Method

Description

void add(Node node, int col, int row)

Adds a node at the specified column and row index.

void add(Node node, int col, int row, int colspan, int rowspan)

Adds a node at the specified column and row index with the specified column and row spans.

void addColumn(int col, Node… nodes)

Adds an entire column of nodes.

void addRow(int row, Node… nodes)

Adds an entire row of nodes.

<ObservableList> getColumnConstraints()

Returns the column constraints. For more information, see to Table 11-6.

<ObservableList> getRowConstraints()

Returns the row constraints. For more information, see Table 11-7.

void setColumnSpan(Node node, int colspan)

Sets the column span for the specified node.

void setRowSpan(Node node, int colspan)

Sets the row span for the specified node.

void setHalignment(Node node, HPos value)

Sets the horizontal alignment for the node. Allowable values are HPos.LEFT , HPos.CENTER , and HPos.RIGHT .

void setValignment(Node node, VPos value)

Sets the vertical alignment for the node. Allowable values are HPos.BOTTOM , HPos.CENTER , and HPos.TOP .

void setHgap(double value)

Sets the size of the gap that appears between columns.

void setVgap(double value)

Sets the size of the gap that appears between rows.

static void setMargin(Node node, Insets value)

Sets the margin for a particular node. See Table 5-2 in Chapter 5 for an explanation of the Insets class.

void setPadding(Insets value)

Sets the padding around the inside edges of the grid pane. See Table 5-2 in Chapter 5 for an explanation of the Insets class.

void setMinHeight(double value)

Sets the minimum height of the grid pane.

void setMaxHeight(double value)

Sets the maximum height of the grid pane.

void setPrefHeight(double value)

Sets the preferred height of the grid pane.

void setMinWidth(double value)

Sets the minimum width of the grid pane.

void setMaxWidth(double value)

Sets the maximum width of the grid pane.

void setPrefWidth(double value)

Sets the preferred width of the grid pane.

To create a basic grid pane, you first call the GridPane constructor. Then, you use the add method to add nodes to the grid pane’s cells. The parameters of the add method specify the node to be added, the node’s column index, and the node’s row index. For example, the following code snippet creates a label, and then creates a grid pane and adds the label to the cell at column 0, row 0:

Label lblName = new Label("Name");
GridPane grid = new GridPane();
grid.add(lblName, 0, 0);

The typical way to fill a grid pane with nodes is to call the add method for each node. However, if you prefer, you can add an entire column or row of nodes with a single call to either addColumn or addRow . For example, this example creates a label and a text field, and then creates a grid pane and adds the label and the text field to the first row:

Label lblName = new Label("Name");
TextField txtName = new TextField();
GridPane grid = new GridPane();
grid.addRow(0, lblName, txtName);

If a node should span more than one column, you can call the setColumnSpan method to specify the number of columns the node should span. For example:

grid.setColumnSpan(txtName, 2);

Here, the txtName node will span two columns. You use the setRowSpan in a similar way if you need to configure a node to span multiple rows.

To control the horizontal alignment of a node, use the setHalignment method as in this example:

grid.setHalignment(lblName, HPos.RIGHT);

Here, the lblName node is right-aligned within its column. The setValignment method works in a similar way.

Like other layout panes, the GridPane class has a host of methods for setting spacing and alignment details. You can use the setHgap and setVgap methods to set the spacing between rows and columns so that your layouts won’t look so cluttered. You can use the setPadding and setMargins methods to set padding and margins, which work just as they do with other layout panes. And you can set the minimum, maximum, and preferred width and height for the grid pane.

Working with grid pane constraints

You can control most aspects of a grid pane’s layouts using methods of the GridPane class, but unfortunately, you can’t control the size of individual columns or rows. To do that, you must use the ColumnConstraints or RowConstraints class, as described in Tables 4-9 and 4-10 .

TABLE 4-9 The ColumnConstraints Class

Constructor

Description

ColumnConstraints()

Creates an empty column constraints object.

ColumnConstraints(double width)

Creates a column constraint with a fixed width.

ColumnConstraints(double min, double pref, double max)

Creates a column constraint with the specified minimum, preferred, and maximum widths.

Method

Description

void setMinWidth(double value)

Sets the minimum width of the column.

void setMaxWidth(double value)

Sets the maximum width of the column.

void setPrefWidth(double value)

Sets the preferred width of the column.

void setPercentWidth(double value)

Sets the width as a percentage of the total width of the grid pane.

void setHgrow(Priority value)

Determines whether the width of the column should grow if the grid pane’s overall width increases. Allowable values are Priority.ALWAYS , Priority.NEVER , and Priority.SOMETIMES .

void setFillWidth(boolean value)

If true, the grid pane will expand the nodes within this column to fill empty space.

void setHalignment(HPos value)

Sets the horizontal alignment for the entire column. Allowable values are HPos.LEFT , HPos.CENTER , and HPos.RIGHT .

TABLE 4-10 The RowConstraints Class

Constructor

Description

RowConstraints()

Creates an empty row constraints object.

RowConstraints(double height)

Creates a column constraint with a fixed height.

RowConstraints(double min, double pref, double max)

Creates a column constraint with the specified minimum, preferred, and maximum heights.

Method

Description

void setMinHeight(double value)

Sets the minimum height of the row.

void setMaxHeight(double value)

Sets the maximum height of the row.

void setPrefHeight(double value)

Sets the preferred height of the row.

void setPercentHeight(double value)

Sets the height as a percentage of the total height of the grid pane.

void setVgrow(Priority value)

Determines whether the height of the row should grow if the grid pane’s overall height increases. Allowable values are Priority.ALWAYS , Priority.NEVER , and Priority.SOMETIMES .

void setFillHeight(boolean value)

If true , the grid pane will expand the nodes within this row to fill empty space.

void setValignment(VPos value)

Sets the horizontal alignment for the entire row. Allowable values are VPos.TOP , HPos.CENTER , and VPos.BOTTOM .

To use column constraints to set a fixed width for each column in a grid pane, first create a constraint for each column. Then, add the constraints to the grid pane’s constraints collection. Here’s an example:

ColumnConstraints col1 = new ColumnConstraints(200);
ColumnConstraints col2 = new ColumnConstraints(200);
ColumnConstraints col3 = new ColumnConstraints(200);
GridPane grid = new GridPane();
grid.getColumnConstraints().addAll(col1, col2, col3);

One of the most useful features of column constraints is their ability to distribute the width of a grid pane’s columns as a percentage of the overall width of the grid pane. For example, suppose the grid pane will consist of three columns and you want them to all be of the same width regardless of the width of the grid pane. The following code accomplishes this:

ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(33);
ColumnConstraints col2 = new ColumnConstraints();
col2.setPercentWidth(33);
ColumnConstraints col3 = new ColumnConstraints();
col3.setPercentWidth(33);
GridPane grid = new GridPane();
grid.getColumnConstraints().addAll(col1, col2, col3);

In this example, each column will fill 33 percent of the grid.

tip Several of the attributes that can be set with column or row constraints mirror attributes you can set for individual nodes via the GridPane class. For example, you can set the horizontal alignment of an individual node by calling the setHalignment method on the grid pane. Or, you can set the horizontal alignment of an entire column by creating a column constraint, setting its horizontal alignment, and then applying the column constraint to a column in the grid pane.

Examining a grid pane example

Listing 4-1 shows the code for a program that displays the scene I drew for Figure 4-8 , and Figure 4-8 shows how this scene appears when the program is run. Figure 4-8 shows that the final appearance of this scene is pretty close to the way I sketched it.

image

FIGURE 4-8: The Pizza Order application in action.

LISTING 4-1 The Pizza Order Application

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.geometry.*;

public class PizzaOrder extends Application
{
public static void main(String[] args)
{
launch(args);
}

Stage stage;
TextField txtName;
TextField txtPhone;
TextField txtAddress;
RadioButton rdoSmall;
RadioButton rdoMedium;
RadioButton rdoLarge;
RadioButton rdoThin;
RadioButton rdoThick;
CheckBox chkPepperoni;
CheckBox chkMushrooms;
CheckBox chkAnchovies;

@Override public void start(Stage primaryStage)
{

stage = primaryStage;
// Create the name label and text field →32
Label lblName = new Label("Name:");
txtName = new TextField();
txtName.setMinWidth(100);
txtName.setPrefWidth(200);
txtName.setMaxWidth(300);
txtName.setPromptText("Enter the name here");

// Create the phone number label and text field →40
Label lblPhone = new Label("Phone Number:");
txtPhone = new TextField();
txtPhone.setMinWidth(60);
txtPhone.setPrefWidth(120);
txtPhone.setMaxWidth(180);
txtPhone.setPromptText("Enter the phone number here");

// Create the address label and text field →48
Label lblAddress = new Label("Address:");
txtAddress = new TextField();
txtAddress.setMinWidth(100);
txtAddress.setPrefWidth(200);
txtAddress.setMaxWidth(300);
txtAddress.setPromptText("Enter the address here");

// Create the size pane →56
Label lblSize = new Label("Size");
rdoSmall = new RadioButton("Small");
rdoMedium = new RadioButton("Medium");
rdoLarge = new RadioButton("Large");
rdoMedium.setSelected(true);
ToggleGroup groupSize = new ToggleGroup();
rdoSmall.setToggleGroup(groupSize);
rdoMedium.setToggleGroup(groupSize);
rdoLarge.setToggleGroup(groupSize);

VBox paneSize = new VBox(lblSize, rdoSmall, rdoMedium, rdoLarge);
paneSize.setSpacing(10);

// Create the crust pane →70
Label lblCrust = new Label("Crust");
rdoThin = new RadioButton("Thin");
rdoThick = new RadioButton("Thick");
rdoThin.setSelected(true);
ToggleGroup groupCrust = new ToggleGroup();
rdoThin.setToggleGroup(groupCrust);
rdoThick.setToggleGroup(groupCrust);

VBox paneCrust = new VBox(lblCrust, rdoThin, rdoThick);
paneCrust.setSpacing(10);

// Create the toppings pane →82
Label lblToppings = new Label("Toppings");
chkPepperoni = new CheckBox("Pepperoni");
chkMushrooms = new CheckBox("Mushrooms");
chkAnchovies = new CheckBox("Anchovies");

VBox paneToppings = new VBox(lblToppings, chkPepperoni,
chkMushrooms, chkAnchovies);
paneToppings.setSpacing(10);

// Create the buttons →92
Button btnOK = new Button("OK");
btnOK.setPrefWidth(80);
btnOK.setOnAction(e -> btnOK_Click() );

Button btnCancel = new Button("Cancel");
btnCancel.setPrefWidth(80);
btnCancel.setOnAction(e -> btnCancel_Click() );

HBox paneButtons = new HBox(10, btnOK, btnCancel);

// Create the GridPane layout →103
GridPane grid = new GridPane();
grid.setPadding(new Insets(10));
grid.setHgap(10);
grid.setVgap(10);
grid.setMinWidth(500);
grid.setPrefWidth(500);
grid.setMaxWidth(800);

// Add the nodes to the pane →112
grid.addRow(0, lblName, txtName);
grid.addRow(1, lblPhone, txtPhone);
grid.addRow(2, lblAddress, txtAddress);
grid.addRow(3, paneSize, paneCrust, paneToppings);
grid.add(paneButtons,2,4);

// Set alignments and spanning →119
grid.setHalignment(lblName, HPos.RIGHT);
grid.setHalignment(lblPhone, HPos.RIGHT);
grid.setHalignment(lblAddress, HPos.RIGHT);
grid.setColumnSpan(txtName,2);
grid.setColumnSpan(txtPhone,2);
grid.setColumnSpan(txtAddress,2);

// Set column widths →127
ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(33);
ColumnConstraints col2 = new ColumnConstraints();
col2.setPercentWidth(33);
ColumnConstraints col3 = new ColumnConstraints();
col3.setPercentWidth(33);
grid.getColumnConstraints().addAll(col1, col2, col3);

// Create the scene and the stage →136
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.setTitle("Pizza Order");
primaryStage.setMinWidth(500);
primaryStage.setMaxWidth(900);
primaryStage.show();

}

public void btnOK_Click() →146
{

// Create a message string with the customer information
String msg = "Customer:\n\n";
msg += "\t" + txtName.getText() + "\n";
msg += "\t" + txtPhone.getText() + "\n\n";
msg += "\t" + txtAddress.getText() + "\n";
msg += "You have ordered a ";

// Add the pizza size
if (rdoSmall.isSelected())
msg += "small ";
if (rdoMedium.isSelected())
msg += "medium ";
if (rdoLarge.isSelected())
msg += "large ";

// Add the crust style
if (rdoThin.isSelected())
msg += "thin crust pizza with ";
if (rdoThick.isSelected())
msg += "thick crust pizza with ";

// Add the toppings
String toppings = "";
toppings = buildToppings(chkPepperoni, toppings);
toppings = buildToppings(chkMushrooms, toppings);
toppings = buildToppings(chkAnchovies, toppings);
if (toppings.equals(""))
msg += "no toppings.";
else
msg += "the following toppings:\n"
+ toppings;

// Display the message
Alert a = new Alert(Alert.AlertType.INFORMATION, msg);
a.setTitle("Order Details");
a.showAndWait();
}

public String buildToppings(CheckBox chk, String msg) →187
{
// Helper method for displaying the list of toppings
if (chk.isSelected())
{
if (!msg.equals(""))
{
msg += ", ";
}
msg += chk.getText();
}
return msg;
}

public void btnCancel_Click() →201
{
stage.close();
}

}

The following paragraphs point out the highlights of this program:

  1. →32: A label and text field are created for the customer’s name.
  2. →40: A label and text field are created for the customer’s phone number.
  3. →48: A label and text field are created for the customer’s address.
  4. →56: A label and three radio buttons are created for the pizza’s size. The label and radio buttons are added to a VBox named paneSize .
  5. →70: A label and two radio buttons are created for the pizza’s crust style. The label and radio buttons are added to a VBox named paneStyle .
  6. →82: A label and three check boxes are created for the pizza’s toppings. The label and check boxes are added to a VBox named paneToppings .
  7. →92: The OK and Cancel buttons are created and added to an HBox named paneButton .
  8. →103: The grid pane layout is created. The padding and horizontal and vertical gaps are set to 10, and the width is set to range from 500 to 800.
  9. →119: The nodes are added to the pane. The name, phone number, and address labels and text fields are added to rows 0, 1, and 2. Then, the size, crust, and toppings VBox panes are added to row 3. Finally, the HBox that contains the buttons are added to column 2 of row 4. ( Remember: Row and column indexes are numbered from 0, not from 1.)
  10. →127: Column constraints are created to distribute the column widths evenly.
  11. →136: The scene is created, and the stage is displayed.
  12. →146: The btnOK_Click method is called when the user clicks OK. This method creates a summary of the customer’s order and displays it using the Alert class.
  13. →187: buildToppings is simply a helper method that assists in the construction of the message string.
  14. →201: The stage is closed when the user clicks the Close button.