Using Layout Containers

In JavaFX SDK 1.2, new layout containers have been incorporated into the programming model for the JavaFX scene graph. These layouts provide flexible ways to arrange the elements of a scene's content. This article discusses the layout mechanisms supported in JavaFX SDK 1.2, considers resizing components, and suggests tips for improving the dynamic layout behavior of your application.

The layout mechanisms available in the JavaFX SDK 1.2 enable you to easily arrange components within a graphical scene. Although you might find absolute positioning more convenient for some applications, using layout containers provides a certain universality and helps you to avoid recalculating coordinates each time the graphical content changes. Consider how to use the container classes to achieve rapid scene layouts.

Concrete Containers

Consider using the concrete layout container classes of the javafx.scene.layout package:

  • HBox - Arranges its content nodes in a single horizontal column.
  • VBox - Arranges its content nodes in a single vertical column.
  • Flow - Arranges its content nodes in either a horizontal or vertical flow, wrapping at its width (for horizontal) or height (for vertical).
  • Stack - Arranges its content nodes in a back-to-front stack.
  • Tile - Arranges its content nodes in uniformly sized layout spaces or "tiles."

These containers manage nodes added to their content instance variable and lay out both visible and invisible nodes by setting the layoutX and layoutY instance variables to position them.

To gain a better understanding of the concrete containers, look at a layout example and explore it in action. To appreciate the differences between the layouts, switch between the layout models.

 

VBox and HBox Classes

These classes arrange nodes in a horizontal row or vertical 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
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.
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 creates a VBox of five images, sets the spacing interval between them, centers images in the column, and places the column in the center of the container.
Source Code
def vbox = VBox{
    nodeHPos: HPos.CENTER // centers nodes horizontally within the column
    hpos: HPos.CENTER // column will be centered within vbox width
    spacing: 15 //set an interval between the elements
    content: [ for (i in [0..4]) ImageView {image: images[i]} ]
}
  

VBox layout Figure 1: Layouts: VBox

This code fragment arranges nine images in a horizontal row, centers the row, and sets the interval between the images.
Source Code

def hbox = HBox{
    vpos: VPos.CENTER // centers the row vertically within the container
    spacing: 10 // sets the an interval between the elements
    content: [ for (i in [0..8]) ImageView {image: images[i]} ]
}
  

HBox layout Figure 2: Layouts: HBox

Stack Class

This class provides an easy way to arrange nodes in z-order creating the back-to-front layers. The first element declared in the content sequence is located at the bottom of the stack, and the last element 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.

Source Code

def stack = Stack{
    content: [ for (i in [0..7]) ImageView {image: images[i]} ]
}
  

Stack Figure 3: Layouts: Stack

Flow Class

The Flow class creates horizontal rows or vertical columns that wrap elements at its 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 enable you configure the alignment of the rows or columns within the flow's width or height.

Note that the Flow layout in the following code fragment could not fit all 16 images into one row. It moves seven elements to the next line and aligns them accordingly. All the images are aligned to the top of the rows.

Source Code
def flow = Flow{
    hgap: 5 // horizontal gap between nodes in a row
    vgap: 10 // vertical gap between rows
    hpos: HPos.CENTER //column will be centered within vbox width
    vpos: VPos.CENTER //centers the row vertically within the container
    nodeVPos: VPos.TOP // images are aligned to the top of a row
    content: [ for (i in [0..15]) ImageView {image: images[i]} ]
}

Flow layout Figure 4: Layouts: Flow

Tile Class

This class arranges nodes within a grid of uniform-sized tiles. The number of tiles is defined by the rows and columns variables. The Tile size defaults to the size needed to encompass the largest preferred width and height of its content nodes. You can alter the tile size by specifying the tileWidth and tileHeight variables. Configurable spacing and alignment are supported. The orientation of the tile layout can be either horizontal (the default) or vertical.

The following code fragment creates a grid of 4x4, sets the gaps between the elements, sets the width of the tile, and centers the container.

Source Code
def tile = Tile{
   columns: 4 // creates a grid of cells
   rows: 4    // 4x4
   tileWidth: 100 // sets the width of the tile
   hgap: 5 //horizontal gap between tiles in a row
   vgap: 5 // vertical gap between rows of tiles
   vertical: true //sets the preferred direction
   hpos: HPos.CENTER //column will be centered within vbox width
   content: [ for (i in [0..15]) ImageView {image: images[i]} ]
}
  

Tile layout Figure 5: Layouts: Tile

The concrete containers are particularly helpful when your layout task matches one of the predefined layout models. If you need to implement a layout different from the models mentioned previously, 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 need to perform layout of the content. The onLayout variable is a function that is called by the scene graph during the layout pass to perform the layout of the panel's content.

Source Code
def p: Panel =  Panel { 
   onLayout: function() { 
      for (n in Container.getManaged(p.content)) { 
         // position/resize nodes
   } 
} 
In the Flower Viewer example, the onLayout function is used to set the position for the images.
Source Code
import javafx.scene.layout.Container.*;

def PanelViews = [ for (i in [0..15]) ImageView {image: images[i]} ];

def panel: Panel = Panel{
    content: PanelViews
    onLayout: function() {
      for (node in getManaged(panel.content)) {
        def i = indexof node;
        positionNode(node, i*40,  i*10);
      }
    }
}

This code fragment produces the following layout.

custom layout Figure 6: Layouts: Panel

Resizing Components Within Containers

The LayoutInfo class defines a set of common layout variables that can be used to specify how a particular node should be laid out within the layout space allocated to it by its parent container.

All of the container classes provided in javafx.scene.layout support LayoutInfo. However, setting layoutInfo on a node that is inside one of these containers is required only when the application needs to override the default layout behavior of that container. An example is to override the preferred sizes of a button so that it is always 100 wide in an HBox container.

Source Code
 HBox {      
    content: [          
       Button {              
          layoutInfo: LayoutInfo { 
	          minWidth: 100 
			  width: 100 
			  maxWidth: 100 
          }          
       }         
       // ..other nodes in hbox      
	]   
} 
   

Improving the Dynamic Behavior 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 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 in sync 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

Rate This Article
Discussion

We welcome your participation in our community. Please keep your comments civil and on point. You may optionally provide your email address to be notified of replies—your information is not used for any other purpose. By submitting a comment, you agree to these Terms of Use.

 

English
日本語
한국어
简体中文
русский
Português do Brasil