Using Layout Containers
- Skill Level Intermediate
- Supported Versions JavaFX 1.3
- Key Features Layouts, HBox, VBox, Tile, Panel, Flow, Stack, Containers
- Last Updated May 2010
The layout models available in the JavaFX SDK enable you to easily arrange content nodes within a graphical scene. Although you might find absolute positioning more convenient for some applications, JavaFX provides layout containers to automate common layout models. This article discusses the layout models supported in JavaFX SDK 1.3 and explains how to use the containers to easily achieve dynamic layout in your application.
Layout Container Classes
Consider using the layout container classes of the javafx.scene.layout package to achieve the different layout models:
HBox- Arranges its content nodes horizontally in a single rowVBox- Arranges its content nodes vertically in a single columnStack- Arranges its content nodes in a back-to-front stackFlow- Arranges its content nodes in either a horizontal or vertical flow, wrapping at the specified width (for horizontal) or height (for vertical)Tile- Arranges its content nodes in uniformly sized layout spaces or tiles
These layout container classes automatically lay out the content nodes in the appropriate container. The nodes are added by using their content instance variables and positioned by setting the layoutX and layoutY variables, which dynamically adjust the node position relative to the container. If any of the context nodes implements the resizable mixin, then the container class sets the width and height variables.
To understand layout containers, look at the following layout example and explore the differences between the layouts by switching between the layout models.
VBox and HBox Classes
HBox and VBox classes arrange nodes in a row or a column. Use the spacing variable to set an offset between the nodes. The following instance variables help you to arrange nodes within these containers.
| VBox | HBox |
|---|---|
nodeHpos – Sets the horizontal alignment of each node within the vbox's columns. Use the HPos class to specify the values. |
nodeVpos – Sets the vertical alignment of each node within the hbox's row. Use the VPos class to specify the values. |
|
|
|
|
|
|
The following code fragment arranges seven images horizontally in a row, centers the row, and sets the interval between the images.
var hbox = HBox {
layoutX: 20
layoutY: 150
nodeVPos: VPos.CENTER //center nodes horizontally within the HBox row.
spacing: 15
content: [for (i in [0..6]) ImageView {image: images[i]}]
}
Figure 1: Layouts: HBox
The following code fragment arranges seven images horizontally in a row, centers images within the row, sets the interval between the images, and sets the left padding.
var hbox = HBox {
layoutX: 20
layoutY: 150
nodeVPos: VPos.CENTER //center nodes within the HBox row.
spacing: 15
padding: Insets{left:50}
content: [for (i in [0..6]) ImageView {image: images[i]}]
}
Figure 2: Layouts: HBox With Left Padding
The following code fragment creates a VBox layout of content nodes, centers the nodes in the column, and sets the spacing interval between them.
var vbox = VBox {
width: 600
layoutX: 280
layoutY: 20
nodeHPos: HPos.CENTER // center nodes horizontally within the column
spacing: 10
content: [for (i in [0..4]) ImageView {image: images[i]}]
}
Figure 3: Layouts: VBox
The following code fragment creates a VBox container of five nodes, centers the nodes in the column, sets the spacing interval between them, and sets the top padding.
var vbox = VBox {
width: 600
layoutX: 280
layoutY: 20
nodeHPos: HPos.CENTER // center nodes horizontally within the column
spacing: 10
padding: Insets{top: 50}
content: [for (i in [0..4]) ImageView {image: images[i]}]
}
Figure 4: Layouts: VBox With Top Padding
Stack Class
The Stack class provides an easy way to arrange overlapping nodes (in z-order), creating the back-to-front layers. The first image declared in the content node sequence is located at the bottom of the stack, and the last image is located at the top. The preferred size of a stack is the size required to accommodate the largest preferred width and height of its content.
The following code example creates a stack of images.
var stack = Stack {
layoutX: 20
layoutY: 20
padding: Insets{top: 50 left:50}
content: [for (i in [0..8]) ImageView {image: images[i]}]
}
Figure 5: Layouts: Stack
Note the vertical and horizontal shift of the stack. This results from using the padding variable, which in this case sets the top and left padding of the container's content node.
Flow Class
The Flow class creates rows or columns that wrap nodes at the container width and height boundaries. You can use the vgap and hgap variables to set gaps between rows and between nodes in a row. The hpos and vpos variables let you configure the alignment of the rows or columns within the flow's width or height.
The Flow container wrapLength variable establishes the preferred width for a horizontal Flow container or the preferred height for a vertical Flow container. The default value is 400.
The following code fragment creates a horizontal flow of 16 images with horizontal gaps between the images and vertical between the rows. The hpos variable aligns each row to the left within the Flow container. The nodeVPos variable aligns each image to the bottom within the row. The Flow class layout in the following code fragment could not fit all 16 images into one row. The wrapLength variable is not explicitly specified, therefore after the 400 width is reached, the application moves the images to the next line, aligns them accordingly, and so forth.
var flow = Flow {
layoutX: 10
layoutY: 20
hgap: 5
vgap: 10
hpos: HPos.LEFT
nodeVPos: VPos.BOTTOM
content: [for (i in [0..15]) ImageView {image: images[i]}]
}
Figure 6: Layouts: Horizontal Flow
If you add the wrapLength variable and specify any value other than 400 (for example, wrapLength: 600), the flow will wrap after the specified width is reached.
The following code fragment creates a vertical flow of 16 images with horizontal gaps between the rows and vertical gaps between the images. The vpos variable aligns each column to the top within the Flow container. The nodeVPos variable aligns each image to the left within the column. The wrapLength variable is explicitly specified, therefore after the 300 height is reached, the application moves the images to the next column, aligns them accordingly, and so forth.
var flow = Flow {
vertical: true
layoutX: 10
layoutY: 20
hgap: 15
vgap: 10
nodeHPos: HPos.LEFT
vpos: VPos.TOP
wrapLength: 300
content: [for (i in [0..15]) ImageView {image: images[i]}]
}
Figure 7: Layouts: Vertical Flow
Tile Class
The Tile class arranges nodes within a grid of uniformly sized tiles. The tiles can be oriented either horizontally (the default) or vertically and, as with the Flow class, the tiles will wrap at the Tile container's width or height boundary. For a horizontal Tile container, the columns variable can be set to establish the preferred width of the container, and for a vertical Tile container, rows can be set to establish its preferred height. Note that the rows and columns variables are used only to calculate the container's preferred size; they will not be updated to reflect the actual number of rows or columns if the container is resized and the flow of tiles changes.
The size of the container's tiles defaults to the size needed to accommodate the largest preferred width and height of its content nodes. You can control the tile size directly by setting the autoSizeTiles variable to false and specifying the tileWidth and tileHeight variables.
The following code fragment creates a horizontally oriented grid with 4 columns. The code automatically recomputes the width and height of the tile, sets the gaps between the images, and inserts left and top padding. Note that the nodeVPos variable aligns the images to the bottom in each row, and nodeHPos variable aligns the images to the left in each column (the default is CENTER for both).
var tile = Tile {
columns: 4
hgap: 5
vgap: 5
padding: Insets{top: 20 left: 20}
nodeVPos: VPos.BOTTOM
nodeHPos: HPos.LEFT
content: [for (i in [0..16]) ImageView {image: images[i]}]
}
Figure 8: Layouts: Tile
The following is an example of a Tile container with the autoSizeTiles variable set to false and tileWidth set to 50.
Figure 9: Layouts: Tile
var tile = Tile {
columns: 4
hgap: 5
vgap: 5
padding: Insets{top: 20 left: 20}
autoSizeTiles: false
tileWidth: 50
content: [for (i in [0..16]) ImageView {image: images[i]}]
}
When you lay out your content in containers, you can also specify the snapToPixel variable. If snapToPixel is true, the following steps are performed:
- The horizontal and vertical position values are rounded to their nearest pixel boundaries before calculating the
layoutXandlayoutYoffsets. - The width and height values are rounded to their nearest pixel boundaries.
Baseline Alignment
Applications often must align GUI controls vertically by their text baselines to achieve attractive interfaces. The HBox, Stack, Tile, and Flow containers provide the VPos.BASELINE variable for this purpose. This alignment can be helpful when arranging GUI controls of different sizes.
The following code aligns different controls by their text baselines.
var textbox = HBox {
layoutX: 20
layoutY: 100
spacing: 10
nodeVPos: VPos.BASELINE
content: [
Label { text: "Name:" }
TextBox {}
Label { text: "Button1" }
Button {text: "OK" }
Label { text: "Button2" }
Button {text: "Cancel" }
Label {text: "Option:" }
RadioButton { toggleGroup: group1 text: "Opt1" selected: true }
RadioButton { toggleGroup: group1 text: "Opt2" }
RadioButton { toggleGroup: group1 text: "Opt3" }
]
}
Figure 10: Layouts: HBox With VPos.BASELINE Vertical Alignment
Figure 11: Layouts: HBox Without VPos.BASELINE Vertical Alignment
The layout containers are particularly helpful when your layout task matches one of the predefined layout models. If you want to implement a different layout from these models, consider creating a custom layout.
Customizing a Layout
The Panel class of the javafx.scene.layout package can be configured as an object literal to create a customized layout. It is recommended that you use this class instead of the Group class when you want to customize the layout of your content. The onLayout variable is a function that is called by the JavaFX scene graph during the layout pass to perform the layout of the Panel class content. In the Flower Viewer example, the onLayout variable is used to set the position for the images.
import javafx.scene.layout.Container.*;
var PanelViews = [ for (i in [0..15]) ImageView {image: images[i]} ];
var panel: Panel = Panel {
content: PanelViews
onLayout: function():Void {
for (node in panel.content) {
def i = indexof node;
positionNode(node, i*40, i*20);
}
}
}
This code fragment produces the following layout.
Figure 12: Layouts: Panel
Resizing Components Within Containers
The Resizable mixin provides a number of functions to identify a given resizable node's sizing preferences, including minimum, preferred, and maximum sizes, as well as fill and grow preferences. The container classes will use these preferences when resizing nodes during layout, however such preferences can be overridden on a per-node basis using the LayoutInfo class.
Setting the LayoutInfo class for a node should be necessary only when the application must override the default preferences of the node and its container. For example, the most common use of LayoutInfo is to override the preferred size of a node, as shown in the following example:
HBox {
content: [
Button {
layoutInfo: LayoutInfo {
minWidth: 100
width: 100
maxWidth: 100
}
}
// ..other nodes in hbox
]
}
Note: The reason to override the Button's preferred size rather than setting the Button's width and height directly is because in JavaFX 1.3 all parent nodes (Groups, Containers, CustomNode) will by default resize any Resizable children to their preferred sizes during layout, overriding any width and height values that might have been set directly by the application.
Improving the Dynamic Layout of Your Application
The layout containers automatically manage the dynamic layout behavior of their content nodes. However, it is often desirable to also use the binding mechanism of the JavaFX Script to establish certain dynamic aspects of layout. For example, you can bind the width and height of an HBox container to the Scene height and width so that whenever Scene resizes, the HBox container resizes as well.
Stage {
var scene: Scene;
scene: scene = Scene {
width: 300
height: 300
content: HBox {
width: bind scene.width
height: bind scene.height
}
}
Another example of using the binding mechanism is centering a container on the screen. The following code fragment keeps the position of the stack synchronized with the size of the scene.
Stage {
var scene: Scene;
var stack: Stack;
scene: scene = Scene {
width: 300
height: 300
content: stack = Stack {
layoutX: bind (scene.width - stack.layoutBounds.width)/2 -
stack.layoutBounds.minX
layoutY: bind (scene.height - stack.layoutBounds.height)/2 -
stack.layoutBounds.minY
content: [...]
}
}
Related Links
- API: JavaFX 1.3 API
- Laying Out GUI Elements
- JavaFX 1.3: Taming the Layout Beast
- JavaFX 1.3: Autosizing Everywhere
Dmitry Kostovarov
Technical Writer, Sun Microsystems