Using FXML
This page was contributed by Gail C. Anderson and Paul Anderson under the UPL and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.Declare Scene Graph Nodes with FXML
You’ve seen how JavaFX APIs create scene graph nodes and configure them for you. The MyShapes
and MyShapesProperties
programs use only JavaFX code to build and configure these objects. An alternative approach is to declare scene graph nodes with FXML, a markup notation based on XML. FXML lets you describe and configure your scene graph in a declarative format. This approach has several advantages:
- FXML markup structure is hierarchical, so it reflects the structure of your scene graph.
- FXML describes your view and supports a Model-View-Controller (MVC) architecture, providing better structure for larger applications.
- FXML reduces the JavaFX code you have to write to create and configure scene graph nodes.
- You can design your UI with Scene Builder. This drag-and-drop tool is a stand-alone application that provides a visual rendering of your scene. And Scene Builder generates the FXML markup for you.
- You can also edit your FXML markup with text and IDE editors.
FXML affects the structure of your program. The main application class now invokes an FXMLLoader
. This loader parses your FXML markup, creates JavaFX objects, and inserts the scene graph into the scene at the root node. You can have multiple FXML files, and typically each one has a corresponding JavaFX controller class. This controller class may include event handlers or other statements that dynamically update the scene.
The controller also includes business logic that manages a specific view.
Let’s return to our MyShapes
example (now called MyShapesFXML
) and use an FXML file for the view and CSS for styling. Below you can see the files in our program, arranged for use with build tools or IDEs.
The JavaFX source code appears under the java subdirectory. The resources subdirectory contains the FXML and CSS files (here Scene.fxml
and Styles.css
).
This program includes a rotating StackPane
, VBox
control, and second Text
object. Scene.fxml
describes our scene graph: a top-level VBox
that includes a StackPane
and Text
element. The StackPane
includes the Ellipse
and Text
shapes.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.effect.DropShadow?>
<?import javafx.scene.effect.Reflection?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.paint.LinearGradient?>
<?import javafx.scene.paint.Stop?>
<?import javafx.scene.shape.Ellipse?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<VBox alignment="CENTER" prefHeight="350.0" prefWidth="350.0" spacing="50.0"
xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx=http://javafx.com/fxml/1
fx:controller="org.modernclient.FXMLController">
<children>
<StackPane fx:id="stackPane" onMouseClicked="#handleMouseClick"
prefHeight="150.0" prefWidth="200.0">
<children>
<Ellipse radiusX="110.0" radiusY="70.0">
<fill>
<LinearGradient endX="0.5" endY="1.0" startX="0.5">
<stops>
<Stop color="DODGERBLUE" />
<Stop color="LIGHTBLUE" offset="0.5" />
<Stop color="LIGHTGREEN" offset="1.0" />
</stops>
</LinearGradient>
</fill>
<effect>
<DropShadow color="GREY" offsetX="5.0"
offsetY="5.0" />
</effect>
</Ellipse>
<Text text="My Shapes">
<font>
<Font name="Arial Bold" size="24.0" />
</font>
<effect>
<Reflection fraction="0.8" topOffset="1.0" />
</effect>
</Text>
</children>
</StackPane>
<Text fx:id="text2" text="Animation Status: ">
<font>
<Font name="Arial Bold" size="18.0" />
</font>
</Text>
</children>
</VBox>
The top-level container includes the name of the JavaFX controller class with attribute fx:controller. The VBox
specifies its alignment, preferred sizes, and spacing followed by its children: the StackPane
and Text
.
Here, we configure the StackPane
with preferred sizing. A special attribute fx:id
specifies a variable name corresponding to this node.
In the JavaFX controller class, you’ll now see this variable name annotated with @FXML
for the StackPane
. This is how you access objects in the controller class that are declared in FXML files.
In addition, StackPane
specifies an onMouseClicked
event handler called #handleMouseClick
. This event handler is also annotated with @FXML
in the JavaFX controller class.
Here, the StackPane
children, Ellipse
and Text
, are declared inside the Children FXML node. Neither has associated fx:id
attributes, since the controller class does not need to access these objects. You also see the linear gradient, drop shadow, and reflection effect configurations.
Note that the Text
object with fx:id text2
appears after the StackPane
definition. This makes the second Text
object appear under the StackPane
in the VBox
. We also specify an fx:id
attribute to access this node from the JavaFX controller.
Controller Class
Let’s show you the controller class now. You’ll notice the code is more compact, since object instantiations and configuration code are no longer done with Java statements. All that is now specified in the FXML markup.
package org.modernclient;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.beans.binding.When;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.util.Duration;
import java.net.URL;
import java.util.ResourceBundle;
public class FXMLController implements Initializable {
@FXML
private StackPane stackPane;
@FXML
private Text text2;
private RotateTransition rotate;
@Override
public void initialize(URL url, ResourceBundle rb) {
rotate = new RotateTransition(Duration.millis(2500), stackPane);
rotate.setToAngle(360);
rotate.setFromAngle(0);
rotate.setInterpolator(Interpolator.LINEAR);
rotate.statusProperty().addListener(
(observableValue, oldValue, newValue) -> {
text2.setText("Was " + oldValue + ", Now " + newValue);
});
text2.strokeProperty().bind(new When(rotate.statusProperty()
.isEqualTo(Animation.Status.RUNNING))
.then(Color.GREEN).otherwise(Color.RED));
}
@FXML
private void handleMouseClick(MouseEvent mouseEvent) {
if (rotate.getStatus().equals(Animation.Status.RUNNING)) {
rotate.pause();
} else {
rotate.play();
}
}
}
The controller class implements Initializable
and overrides method initialize()
, which is invoked for you at runtime. Importantly, the private class fields stackPane
and text2
are annotated with @FXML
.
The @FXML
annotation associates variable names in the controller class to the objects described in the FXML file. There is no code in the controller class that creates these objects because the FXMLLoader
does that for you.
The initialize()
method does three things here. First, it creates and configures the RotateTransition
and applies it to the stackPane
node. Second, it adds a change listener to the transition’s status property.
And third, a bind expression for the text2
stroke property specifies its color based on the rotate transition’s status.
The @FXML
annotation with handleMouseClick()
indicates that the FXML file configures the event handler. This mouse click event handler starts and stops the rotate transition’s animation.
JavaFX Application Class
The main application class, MyShapesFXML
, now becomes very simple. Its job is to invoke the FXMLLoader
, which parses the FXML (Scene.fxml
), builds the scene graph, and returns the scene graph root. All you have to do is build the scene object and configure the stage as before, as shown below.
package org.modernclient;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class MyShapesFXML extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass()
.getResource("/fxml/Scene.fxml"));
Scene scene = new Scene(root, Color.LIGHTYELLOW);
scene.getStylesheets().add(getClass()
.getResource("/styles/Styles.css").toExternalForm());
stage.setTitle("MyShapesApp with JavaFX");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Adding CSS
Now let’s show you how to incorporate your own styles with CSS. One advantage of JavaFX is its ability to style nodes with CSS.
JavaFX comes bundled with a default stylesheet, Modena.css
. You can augment these default styles or replace them with new ones. Our example CSS file found in file Styles.css is a single style class (mytext
) that sets its font style to italic:
.mytext {
-fx-font-style: italic;
}
To use this stylesheet, you must first load the file, either in the application’s start()
method or in the FXML file. Once the file is added to the available stylesheets, you can apply the style classes to a node. To apply individually defined style classes to a specific node, for instance, use:
text2.getStyleClass().add("mytext");
Here, mytext
is the style class and text2
is the second Text
object in our program.
Alternatively, you can specify the stylesheet in the FXML file. The advantage of this approach is that styles are now available inside Scene Builder. Here is the modified Scene.fxml
file that loads this customized CSS file and applies the customized CSS style class to the text2 Text
node:
. . .
<VBox alignment="CENTER" prefHeight="350.0" prefWidth="350.0" spacing="50.0"
stylesheets="@../styles/Styles.css"
xmlns="http://javafx.com/javafx/10.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.modernclient.FXMLController">
<children>
<StackPane fx:id="stackPane" onMouseClicked="#handleMouseClick" prefHeight="150.0" prefWidth="200.0">
. . . code removed . . .
</StackPane>
<Text fx:id="text2" styleClass="mytext" text="Animation Status: ">
<font>
<Font name="Arial Bold" size="18.0" />
</font>
</Text>
</children>
</VBox>
Using Scene Builder
Scene Builder was originally developed at Oracle and is now open sourced. It is available for download from Gluon here: https://gluonhq.com/products/scene-builder/.
Scene Builder is a stand-alone drag-and-drop tool for creating JavaFX UIs. You can see below the main Scene Builder window with file Scene.fxml
from the MyShapesFXML
program.
The upper-left window shows the JavaFX component library. This library includes containers, controls, shapes, 3D, and more. From this window, you select components and drop them onto your scene in the middle visual view or onto the Document
window shown in the lower-left area.
The Document
window shows the scene graph hierarchy. You can select components and move them within the tree. The right window is an Inspector
window that lets you configure each component, including its properties, layout settings, and code. In this figure, the StackPane
is selected in the Document
hierarchy window and appears in the center visual view.
In the Inspector
window, the OnMouseClicked
property is set to #handleMouseClick
, which is the name of the corresponding method in the JavaFX controller class.
Scene Builder is particularly helpful when building real-world form-based UIs. You can visualize your scene hierarchy and easily configure layout and alignment settings.
Last update: September 12, 2023