Using Layout Containers

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 row
  • VBox - Arranges its content nodes vertically in a single column
  • Stack - Arranges its content nodes in a back-to-front stack
  • Flow - 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.

hpos – Sets the horizontal alignment of the rows within this container's width. Use the HPos class to specify the values.

vpos – Sets the vertical alignment of the columns within this container's height. Use the VPos class to specify the values.

padding - Specifies the top, right, bottom, and left padding around the container's content. This space is included in the calculation of the container's minimum and preferred sizes.
spacing - Specifies the amount of horizontal space between each content node in the container.

The following code fragment arranges seven images horizontally in a row, centers the row, and sets the interval between the images.

Source Code
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]}]
}

HBox layout 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.

Source Code
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]}]
}

HBox layout 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.

Source Code
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]}]
}

VBox layout 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.

Source Code
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]}]
}

VBox layout 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.

Source Code
var stack = Stack {
layoutX: 20
layoutY: 20
padding: Insets{top: 50 left:50}
content: [for (i in [0..8]) ImageView {image: images[i]}]
}

Stack 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.

Source Code
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]}]
}

Flow layout 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.

Source Code
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]}]
}

Flow layout 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).

Source Code
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]}]
}

Tile layout 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.

Tile layout Figure 9: Layouts: Tile

Source Code
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 layoutX and layoutY offsets.
  • 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.

Source Code
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" }
]
}

HBox layout Figure 10: Layouts: HBox With VPos.BASELINE Vertical Alignment

HBox layout 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.

Source Code
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.

custom 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:

Source Code
 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.

Source Code
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.

Source Code

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