Every application with more than one button needs to organise its nodes in some way. JavaFX’s default layouts can achieve this with little to no effort, and with surprising flexibility. In fact, from dashboards with multiple charts, to games with terrain, characters and user controls, JavaFX’s layout classes can achieve an impressive variety of effects.
Layouts in JavaFX are containers that provide pre-built functionality to define the position, size and alignment of nodes such as charts, images and controls. JavaFX provides 9 default layout classes, including the base class
Pane, which provides only for absolute positioning.
Before I start, if you just need to see how to use one type of pane, feel free to jump ahead:
There are numerous tutorials on the high-level functionality of all 9 layout panes, including from Oracle itself. What I’ll try to add with this article is a more thorough summary of the quirks, and how you can avoid (or use) them to make your user interfaces responsive, but predictable. I’ve broken this tutorial down into two sections:
There are three nodes I won’t talk about in this article:
SplitPane. The primary use of these panes is to allow the user to control the viewport through which to view nodes, and not to lay them out.
Choosing a layout should depend on two factors:
A good guiding principle for choosing a layout is to think about the general position you want for nodes in a layout, and to select a type of layout based on that.
Once you’ve chosen your layout type, you can fine-tune your choice based on the control you need over positioning, sizing and alignment of your nodes.
The layouts can be broken down into three types: block, grid and row/column layouts.
Each group thinks about nodes in a different way. And within a block, organises the finer-tuning of node positions differently.
Block layouts are the simplest, having only one set of rules that defines the layout of their children. I’ve called them ‘block’ layouts because they do not segregate nodes into areas (grid layouts) or runs (row/column layouts).
That is to say, from the perspective of the layout logic, the pane is a single block.
In the case of the
StackPane, node position is controlled through setting alignment, whereas the
AnchorPane uses absolute positioning.
The AnchorPane’s absolute positioning means it can control both position and size, whereas the
StackPane can only control position.
Grid layouts are the most complex, and can allow for very detailed control of the position of nodes.
TilePane class is the easiest to use, but offers the least control over the positioning of the nodes.
In the case of the
BorderPane, users are granted coarse control over positioning, but are also guaranteed certain behaviour by default. This can be a good balance for default window-behaviour use cases.
GridPane allows for significant breadth in fine control of node position, but can be difficult and complex to use.
Finally, row and column layouts are designed to display nodes in a specified order, either vertically or horizontally.
Each of these comes with options to control the spacing between the nodes in the series. In the case of the FlowPane, users can also control the space between rows or columns in multi-row and multi-column settings.
Within a layout type, there are usually multiple ways to layout nodes in the same way. So, once I’ve chosen the rough positions of nodes, I tend to choose layouts based on how I want each component to behave when the layout pane is resized.
Panes are designed to control the position, size and alignment (relative position) of children in a variety of ways. These affect how it will move and resize your nodes when the pane is resized
It’s always worth bearing in mind that windows are frequently resized by users significantly smaller or larger than you might have planned.ff
|Pane||Layout Positions Children||Resizes Children||Aligns Children||Has sub-regions||Children can overlap|
One common issue with using layouts is trying to resize controls. If you’re having trouble getting your layout to resize a
Control like a
Button, take a look at the dropdown to fix it.
I have spent hours googling why something won’t expand to fit an HBox in my time. If it’s a
Control, the default behaviour is for the maximum size to reflect the preferred size. So the
Control won’t stretch.
To fix this, you’ll need to modify the maximum width and height.
Set the width and height to
Double.MAX_VALUE. This can be done programatically by invoking
setMaxHeight(Double value) and
setMaxWidth(Double value). It can be also be done in FXML by setting
If you want your
Control to resize, but you don’t want it to look stupidly large, use
setMaxHeight(Double value) and
setMaxWidth(Double value) to specify a real maximum.
This will override the control’s default behaviour not to resize. You can also do this with FXML. If you want it to resize to a maximum size of 200 px, simply set
Once you’ve chosen a layout, you’ll want to use it… In the next section, I’ll run through how to control the size, position and alignment of nodes in each of JavaFX’s layouts.
Pane class is the base class for all layouts and will not apply any layout behaviour to its children. For this reason, the
Pane is useful when you need to position a node absolutely, but you don’t want any additional resizing behaviour, or alignment.
Each layout pane extends the Pane class and provides additional functionality layered on top of the core behaviour defined in the
Pane. There isn’t any magic to layout panes: they achieve the layout behaviour they want by manually calculating the
layoutY properties of every child node according to a set of rules specific to the layout they’re trying to create.
In each of the sections below, I’ll go through the behaviour and how the rules can help you use these layouts as they were designed.
I’ll go through them by Type.
These are incredibly useful for deciding yourself where nodes should be displayed, and are frequently useful in designing custom content areas.
AnchorPane class is a simple layout with three guiding principles:
In addition to absolute positioning using
AnchorPane has four methods that define the layout of its children.
These define the absolute position of the edge of a child with respect to the same edge of the AnchorPane.
These methods can be used for both positioning and sizing nodes.
To change the order overlapping nodes are rendered, use
getChildren() to get the list of children, and change the order.
Setting one or two anchors on a node will have the effect of positioning the node with respect to the layout bounds of the
Setting an anchor to zero will glue the specified side of a node to the same side of the
Warning: Anchor-setting methods accept double values, which can be negative, but it might not have the effect you think.
If I try setting a negative left anchor, I might expect that the node would look like it’s sliding off the left edge of the
AnchorPane. Instead, the
AnchorPane tries to resize itself to accommodate the node.
The top node, for which I set a negative anchor, still looks like it’s glued to the left of the pane. And the other nodes just look like they’ve moved right instead.
To successfully accomplish the desired effect of having an item sliding out of the left hand side of an
AnchorPane, you’ll need to combine sliding with clipping.
Setting three anchors on a node will have the effect of stretching the node between the two opposing anchors.
This can be extremely useful because it overrides any maximum or minimum width or height on the node.
You can stretch nodes in both directions by using all four anchor points.
StackPane is a simple layout pane similar to the
AnchorPane. Both panes are relatively simple layouts that support rendering of nodes that overlap each other. However, where the
AnchorPane focused on absolute positioning, the
StackPane focuses on alignment-based positioning of nodes.
To make the most of the
StackPane, I usually sort alignment and then positioning, so I in this case, I’ll go through alignment first, and then positioning.
StackPane also won’t resize nodes. Unlike the
AnchorPane, which can stretch nodes by enforcing absolute positioning on a child’s bounds, the StackPane’s focus on alignment means it won’t adjust the size of any nodes. So, no sizing section here either.
By default, all nodes are aligned in the centre of the StackPane.
To change the default alignment of nodes, you will need to use the method
setAlignment(Pos value) on the
StackPane in question.
To get nodes exactly where you want them, the temptation is to modify the position of object using the
translateY properties. In fact when I started with StackPanes, I regularly hooked in listeners to the StackPane width that would adjust the translate properties of nodes. Stop! And use margins instead.
To adjust nodes positions, the static method
StackPane.setMargin(Node child, Insets insets) can be called, referencing the node to align, and the preferred alignment.
StackPane does not support absolute positioning of nodes, and any values of
layoutY are ignored by the
StackPane. You can fine-tune the positions of nodes by setting margins on a node-by-node basis, or by changing the
translateY values of nodes.
I think the safest way to work with the StackPane is to use margins rather than manually modifying the translate properties of an object. In this case, you can manually create a transparent margin around each node by invoking the static method
StackPane.setMargin(Node node, Insets margin).
I usually find this manages the tweaking I need, although you can manually modify the horizontal and vertical position yourself.
You can make adjustments o the position of child nodes by using the
translateY properties. This is especially useful for animations, or transitions where you’re adding or removing nodes.
These can be quite useful but bear in mind they’re applied after the layout bounds of the
StackPane have been calculated, so nodes with modified translate properties may be rendered outside the bounds of the
Tip: When a translate property is modified, it is applied after all alignment and insets, so remember to take these into account when you’re calculating the amount to modify these properties.
Grid layouts are useful for giving the user a familiar experience, by creating interfaces they’ve seen before, such as windows (border panes), galleries (tile panes) and forms (grid panes).
BorderPane is designed to have top and bottom regions of fixed height, and left and right regions of fixed width. The effect is to have a centre of varying size as the
BorderPane changes dimensions.
Let’s go through how to set positions, sizing and alignment with the
Unlike the panes we’ve seen to far, which have used the
getChildren() method to access the children of the node, this won’t work with the
As a result of the design intention to host children in a specific way, we need to tell the
BorderPane which area to add the node to. The five methods for setting nodes in the
Only one node can be stored in each region of the
BorderPane, so calling any of these methods multiple times will remove the original node, and replace it with the most recent assignment.
The sizing behaviour of the BorderPane is one of the reasons it is particularly good for a standard window layout. It’s designed to accommodate a menu bar of a fixed height but a variable width, and a navigation window of a fixed width but varying height.
Because of that exact design intention, the BorderPane’s effect on node size depends on where in the
BorderPane the node is placed. Here are the specifics about how the
BorderPane sizes its children:
Top and Bottom
Height: The nodes in the top and bottom panes are fixed at their preferred height.
Width: The width of nodes in the top and bottom regions is adjusted within the its resizeable range dependant on the width of the
If the BorderPane is asked to shrink below the minimum width of the top/bottom nodes, they’re still rendered faithfully at their minimum size.
Left and Right
Height: The height of nodes in the left and right regions is adjusted within the its resizeable range dependant on the height of the
BorderPane. It is calculated as the height of the
BorderPane minus the heights of the top and bottom regions.
BorderPane is asked to shrink below the minimum height of the left/right nodes, they’re still rendered faithfully at their minimum size.
Width: The nodes in the left and right panes are fixed at their preferred width.
Width: the width of the center region is calculated as the width of the
BorderPane minus the preferred width of the right and left nodes added together.
Height: the height of the center region is calculated as the height of the border pane minus the preferred height of the top and bottom nodes added together.
BorderPane is asked to shrink below the minimum height or width of the centre node, it’ll still be rendered faithfully at its minimum size.
The BorderPane will create space around nodes in their regions under the following circumstances:
- The width of the BoderPane is larger than the maximum width of a top or bottom node.
- The height of the BorderPane is larger than the maximum height of a left or right node.
- The space remaining in the middle is larger than the maximum dimensions of the centre node.
If the size of any
BorderPane region is greater than the maximum size of the node ir contains, it will be positioned according to the following rules by default:
The alignment of any node within the
BorderPane region can be set using the static method
BorderPane.setAlignment(Node node, Pos alignment) method, or by setting
BorderPane.alignment="<alignment>" in FXML.
GridPane layout provides the finest level of control for nodes in a grid layout. This includes capability to set column- and row-specific properties for alignment and sizing, in addition to node-specific row- and column-spanning behaviour.
GridPanes are the most complex of JavaFX’s default layouts, with multiple layers of positional and sizing control.
These can be added to a GridPane by calling
getColumnConstraints().add(ColumnConstraints constraint) or
Constraints are added to columns and rows in the order they’re added to the
GridPane. Adding empty constraints such as
new ColumnConstraints() to a
GridPane will have the effect of not setting any constraints. This is useful if you want to skip over rows or columns.
Positioning content in a GridPane can be achieved in four ways:
Setting the cell in which a Node should be placed
At the coarsest level, positioning a node is achieved by setting its location in the grid. This can be achieved by setting its row location, column location, or both simultaneously.
//set both positions simultaneously GridPane.setConstraints(myNode, 0, 1); //set column position GridPane.setColumnIndex(myNode, 0); //set row position GridPane.setRowIndex(myNode, 1);
Altering the size of a column or a row
By default, column and row sizes will be calculated based on the preferred width and height of their contents. This behaviour can be overridden by creating
RowConstraints objects, and applying them to the
RowConstraints object, the height of a row can be specified as either pixel dimensions using
setPrefHeight(Double value) or a percent of the gridpane’s total height using
Similar methods are available for the
Spanning columns and rows
The position of a node inside the grid can be changed by instructing the
GridPane to set it to span multiple columns or rows.
This is set using the static
GridPane.setRowSpan(Node child, Integer value) and
GridPane.setColumnSpan(Node child, Integer value).
Padding between rows and columns
Finally, the position of nodes can be tuned by altering the padding between rows and columns of a
These can be set using
setHgap(double value) and
setVgap(double value) in addition to the normal padding around the edge of the GridPane, which can be set using
Sizing nodes and columns can again be successfully achieved in multiple ways. Both ways require the creation of
By default, a
GridPane will accommodate nodes at their preferred size, and will not stretch them to fill columns. It will also expand all columns and rows equally into available space so this is useful for tuning positional behaviour.
Filling rows and columns to fill unoccupied space
A row can be instructed to fill any unoccupied space by creating a
RowConstraints object, and invoking
Likewise, a column can be instructed to fill any unoccupied space by creating a
ColumnConstraints object, and invoking
Space is distributed between rows and columns using the following logic:
- Distribute space evenly between all rows or columns with
- If no columns/rows have
Priority.ALWAYS, or space is remaining, distribute the remaining space between rows or columns with
Growing nodes vertically and horizontally into available space within their cell
A row can be instructed to grow all nodes vertically into unoccupied space by creating a
RowConstraints object (or modifying an existing one), and invoking
Again, in a similar way, a column can be instructed to fill any unoccupied horizontal space by creating a
ColumnConstraints object (or modifying an existing one), and invoking
Finally, nodes within rows can be instructed to align vertically (and within columns, align horizontally). These are set using the
One last time, a row can be instructed to align nodes vertically by creating a
RowConstraints object (or modifying an existing one), and invoking
And a column can be instructed to align nodes horizontally by creating a
ColumnConstraints object (or modifying an existing one), and invoking
TilePane calculates its layout by adding tiles (virtual regions inside which our nodes get rendered) in one dimension and then wrapping tiles into additional rows or columns. Tiles are of a consistent size throughout the pane.
Varying the position, size and alignment of nodes in side a TilePane can be achieved by tuning the padding between tiles, or by setting node-specific margins.
The distance between nodes can be adjusted by setting the
vgap properties of the
TilePane instance that contains our nodes.
Tile sizes can also be changed as a method of altering the positioning of nodes, although users should be aware that this will also change the sizes of nodes (within their resizeable range).
Tile sizes can be altered by invoking
setPrefTileWidth(double value) and
setPrefTileHeight(double value). These sizes aren’t guaranteed, and if the
TilePane is reduced in size to a point where it cannot accommodate the tiles, they will be reduced in size.
Unlike many other layouts, the
TilePane will by default try to stretch tiles that are smaller than the tile size within their resizeable range.
tilePrefHeight properties are smaller than the preferred dimensions of the nodes, it will also attempt to shrink nodes that are larger within their resizeable range.
If the default tile size is greater than the maximum size of any node, the node will be distributed within the tile according to the alignment it has been set.
The alignment is set on a per-node basis using the static
TilePane.setAlignment(Pos value). The default alignment is
Pos.CENTER, although if you use the static method
TilePane.getAlignment(Node node) on a node where it has not been explicitly set, it will return null (this is also true for other panes).
Row and column layouts display their content in an order across a single dimension. The
FlowPane wraps content that does not fit in the primary dimension by creating additional rows or columns. VBoxes and HBoxes do not.
FlowPane is designed to give your layout a responsive feel, changing the length of a row or column to suit the size of the
FlowPane itself. It’s great for similar-looking items that need to be shuffled around.
By default, the
FlowPane will fill nodes into rows, and then make additional rows if necessary. For a
FlowPane that produces columns of content rather than rows, create a
FlowPane and call
A FlowPane won’t try to resize nodes, so there’s no sizing section here.
Because FlowPanes can be used to pack items into rows or columns, it internally refers to them as ‘runs’. If the orientation is horizontal, a run is horizontal. If it’s vertical, a run is vertical.
The basic rule of FlowPanes is this: make a run as long as you can before the next node would go over the maximum allowed length, and then use that node as the first item in the next run.
You can set the margin of a
FlowPane by invoking
setMargin(Insets insets) to create a space around the contents where it will not try to fill nodes. This uses an
Insets object, which can specify margins for top, bottom, left and right separately, or together.
You can determine where the FlowPane will allocate leftover space is allocated by using the method
setAlignment(Pos value). The image above shows a vertical
FlowPane with a
Pos.TOP_LEFT alignment, meaning the space is distributed to the bottom and the right.
You can control the length of a run by invoking
setPrefWrapLength(Double value), which works regardless of the orientation you’ve set.
Vertical and horizontal gaps
FlowPane doesn’t distinguish between orientation when setting gaps, so they are absolute. That means the gap between vertical elements is set using
setVgap(Double value) and the gaps between horizontal elements is set using
This can get confusing if you start switching orientations if you wanted the functionality to differentiate between runs and items-in-a-run.
Gap between items in a run
For a horizontal
FlowPane (default), use
setVgap() and for a vertical
Gap between runs
For a horizontal
FlowPane (default), use
setHgap() and for a vertical
If nodes of different sizes are included in the FlowPane, the FlowPane will make runs to accommodate the largest member of that run. That means if it’s a vertical FlowPane, it’ll make runs wide enough for the widest member. If it’s a horizontal FlowPane, it’ll make runs tall enough for the tallest member.
Frustratingly, the FlowPane asks that you set the alignment of columns (in a vertical orientation) and rows (in a horizontal orientation) separately.
FlowPane with a vertical orientation, using a
HPos.LEFT column alignment.
The HBox positions nodes horizontally from left to right. Node resizing and alignment preferences can be set, while the order of elements can be changed by altering the order of elements in the
HBox has a single run of horizontal nodes. However, they have more control over the size of their children.
The horizontal order of the child nodes is left-to-right, and is the same as the order of the items in the
ObservableList By default the order of the nodes is the order the children were added.
Sizing can be accomplished both horizontally and vertically for the HBox.
HBox will by default resize its children vertically to their maximum allowed height (bounded by the height of the
HBox itself, and their maximum height property). This functionality can be turned off by invoking
This is set for an HBox instance, and will apply to all nodes in that HBox.
Nodes can request to be resized to fill any additional horizontal space, on a node-by-node basis.
A node can request to fill any remaining horizontal space inside an
HBox by calling the static method
Hbox.setHgrow(Node node, Priority value).
Priority is an enum with the values
NEVER. Space is distrubuted in the following way:
- Distribute space evenly between all nodes with
- If no nodes have
Priority.ALWAYS, or space is remaining, distribute the remaining space between nodes with
- Allocate the remaining space according to the HBox’s alignment
You can set the alignment of the
HBox by calling
The VBox positions nodes vertically, top-to-bottom. I’ll give you the whistle-stop tour, but checkout the HBox drop-down if you want a more detailed guide to the functionality. The internal logic is identical, but horizontal rather than vertical.
VBox has a single run of vertical nodes. Just like the
HBox, is has more control over the size of its children than a vertical
Sizing can be accomplished both horizontally and vertically for the
To stop a
VBox stretching its children horizontally, use
setFillWidth(false). This is set for an
VBox instance, and will apply to all nodes in that
You can request that nodes stretch to fit any remaining vertical space in a
VBox by setting
Vbox.setHgrow(Node node, Priority value). Space is distributed using the same rules as for an
HBox (but vertically…)
You can set the alignment of the VBox by calling
JavaFX has 9 pre-defined containers that provide functionality to define the position, size and alignment of nodes such as charts, images and controls. JavaFX’s layouts can achieve a huge variety of functionality and positional control.
There are three basic types of layouts: grids, rows and blocks. They define the way the layout ‘thinks’ about positioning your nodes. Sketching out a container and using this to decide the sort of layout you need is usually a great way to start.
Once you’ve chosen the basic way you want your pane to look (a grid, a row or a block) I’d recommend choosing your layouts based on how they respond to being resized. Unless you’ve made them explicitly fixed-size, users will always surprise you with bugs they pick up making windows ridiculously big (or small).