Building GUI Applications With JavaFX

Lesson 6: Laying Out GUI Elements


Consider creating three circles and a toggle group of radio buttons. Each radio button controls a color in the corresponding circle. When the radio button is selected the color is applied, otherwise the circle is gray. This scenario simulates a traffic light and is the example used in this tutorial to describe the layout mechanism.

Traffic Lights: Stop
Figure 1: Stop Light
Traffic Lights: Ready
Figure 2: Ready Light
Traffic Lights: Go
Figure 3: Go Light

To create a window:

  1. Add an import statement for the Stage and Scene classes.

  2. Add the Stage object literal.

  3. Specify the title of the window.

  4. Add the Scene object literal to the scene instance variable of the Stage class.

  5. Set the width and the height of the scene.
import javafx.stage.Stage;
import javafx.scene.Scene;

Stage {
title: "Traffic lights"
scene: Scene{
width: 210
height: 90
}//Scene
}//Stage

Create a group of radio buttons in which only one button can be selected at a given time. This is called a toggle group. To define radio buttons:

  1. Add import statements for the ToggleGroup and RadioButton classes.

  2. Define the toggle group by using the ToggleGroup class.

    Note: In the example code for this lesson, you define a variable name for each UI element so that you can easily learn how the layout mechanism works. You also apply binding to the UI elements by referring to the variable names.

  3. Define the choiceText sequence for the button captions.

  4. Create a sequence of radio buttons within a cycle construction. The buttons enable selection of the particular traffic light by using the RadioButton class.

  5. Add each radio button to the group by using the toggleGroup instance variable.

The following code fragment defines a toggle group, then creates radio buttons that add them to the group.

import javafx.scene.control.ToggleGroup;
import javafx.scene.control.RadioButton;

def group = ToggleGroup{};
def choiceText = ["STOP", "READY", "GO"];

def choices = for (text in choiceText)
RadioButton{
toggleGroup: group
text: text
}//RadioButton

To draw circles that indicate the traffic lights:

  1. Add import statements for the Circle, Color, RadialGradient, and Stop classes.

  2. Define the colors sequence for the colors of the circles.

  3. Create a sequence of circles within a cycle construction.

  4. Apply radial gradient filling to visually enhance the example.

    import javafx.scene.shape.Circle;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;

    var colors = ["RED", "GOLD", "GREEN"];

    var lights = for (color in colors)
    Circle {
    centerX: 12
    centerY: 12
    radius: 12
    stroke: Color.GRAY
    fill: bind RadialGradient {
    centerX: 8,
    centerY: 8,
    radius: 12,
    proportional: false
    stops: [
    Stop {offset: 0.0 color: Color.WHITE},
    Stop {offset: 1.0 color:
    if (choices[indexof color].selected)
    then Color.web(color)
    else Color.GRAY
    }//Stop
    ]
    }//RadialGradient
    }//Circle

    The preceding code sample uses the data binding mechanism to change the color of the circle. If the selected instance variable of choices[1] is true, the color instance variable becomes Color.RED, otherwise it is Color.GRAY. Refer to Creating Graphical Objects for more information about shapes and visual effects.

After all the components are created, you can use layout containers to arrange elements within the scene. This lesson explores the JavaFX Container: HBox, VBox, and Tile. In this example, you need a VBox object to lay out the radio buttons, an HBox object for the circles, and another HBox to arrange those two boxes.

A combination of HBox and VBox
Figure 4: Combination of HBox and VBox

To lay out the circles horizontally:

  1. Add an import statement for the HBox class.

  2. Arrange the circles within the horizontal box.

  3. Specify the spacing variable to set an offset between the circles.

  4. Set the translateY variable to 25 so that the horizontal box can be located drawn down.

      HBox{ spacing: 15 content: lights translateY: 25}

To arrange the radio buttons:

  1. Add an import statement for the VBox class.

  2. Use the cycle construction to add radio buttons to the container's content.

  3. Set the vertical offset between the radio buttons by using the spacing variable.

    VBox{
    spacing: 10
    content: [for (i in [0..2]) choices[i] ]
    }

  4. Use another HBox container to lay out the vertical box with the radio buttons and the horizontal box with the circles.

Note: All the Container classes constrain their children classes to fit a particular size, unlike the Group class that keeps its children size intact. Hence, if you would like to change a size of the RadioButton added to the VBox, its size is not honored, because the container resizes the node to its preferred size. To change the predefined size of the UI control use the LayoutInfo class as follows:

RadioButton{
toggleGroup: group
text: text
layoutInfo: LayoutInfo { width: 150 }
}//RadioButton

See the complete code of the example as follows.

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ToggleGroup;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.control.RadioButton;

def group = ToggleGroup{};
def choiceText = ["STOP", "READY", "GO"];
def colors = ["RED", "GOLD", "GREEN"];

def choices = for (text in choiceText)
RadioButton{
toggleGroup: group
text: text
}//RadioButton

def lights = for (color in colors)
Circle {
centerX: 12
centerY: 12
radius: 12
stroke: Color.DARKGRAY
fill: bind RadialGradient {
centerX: 8,
centerY: 8,
radius: 12,
proportional: false
stops: [
Stop {offset: 0.0 color: Color.WHITE},
Stop {offset: 1.0 color:
if (choices[indexof color].selected)
then Color.web(color)
else Color.GRAY
}//Stop
]
}//RadialGradient
}//Circle

Stage {
title: "Traffic lights"
scene: Scene{
width: 210
height: 90
content:
HBox{ spacing: 20
content:[
VBox{ spacing: 10
content: [for (i in [0..2])
choices[i]
]
}
HBox{ spacing: 15 content: lights translateY: 25}
]
}//HBox
} //Scene
}//Stage

When compiled and run, this code produces the following window.

A vertical box with radio buttons and a horizontal box with circles
Figure 5: Vertical Box With Radio Buttons and Horizontal Box With Circles

Alternatively you can use another layout manager represented by the Tile class to arrange the same components, but in a slightly different order. The Tile layout arranges elements in a grid of cells. Each cell is exactly the same size. The rows and columns instance variables define the grid's size.

Tile layout
Figure 6: Structure of the Tile Layout

To apply the Tile layout:

  1. Add an import statement for the Tile class.

  2. Add the Tile object literal to the scene's content.

  3. Define the number of rows and columns in the grid.

  4. Use a cycle construction to arrange elements within the grid so that each row contains a radio button and a circle.

  5. Define the amount of vertical space between elements in a column by using the vgap variable.

The following code fragment shows how to create a Tile object with three rows and two columns.

Tile {
columns: 2
rows: 3
vgap: 5
content: for (i in [0..2])
[choices[i], lights[i]]
}//Tile

See the complete code of the example as follows.

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.RadioButton;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.layout.Tile;

def group = ToggleGroup{};
def choiceText = ["STOP", "READY", "GO"];
def colors = ["RED", "GOLD", "GREEN"];

def choices = for (text in choiceText)
RadioButton{
toggleGroup: group
text: text
}//RadioButton

def lights = for (color in colors)
Circle {
centerX: 12
centerY: 12
radius: 12
stroke: Color.DARKGRAY
fill: bind RadialGradient {
centerX: 8,
centerY: 8,
radius: 12,
proportional: false
stops: [
Stop {offset: 0.0 color: Color.WHITE},
Stop {offset: 1.0 color:
if (choices[indexof color].selected)
then Color.web(color)
else Color.GRAY
}//Stop
]
}//RadialGradient
}//Circle

Stage {
title: "Traffic lights"
scene: Scene{
width: 210
height: 90
content:

Tile {
columns: 2
rows: 3
vgap: 5
content: for (i in [0..2])
[choices[i], lights[i]]
}//Tile

} //Scene
}//Stage

When compiled and run, the modified application produces the following window.

Tile layout
Figure 7: Tile Layout of the Radio Button and Circles

Try to lay out other components, for example, buttons.

0 comments:

Post a Comment