JavaFX FXML
Jakob Jenkov |
JavaFX FXML is an XML format that enables you to compose JavaFX GUIs in a fashion similar to how you compose web GUIs in HTML. FXML thus enables you to separate your JavaFX layout code from the rest of your application code. This cleans up both the layout code and the rest of the application code.
FXML can be used both to compose the layout of a whole application GUI, or just part of an application GUI, e.g. the layout of one part of a form, tab, dialog etc.
JavaFX FXML Example
The easiest way to start learning about JavaFX FXML is to see an FXML example. Below is a FXML example that composes a simple JavaFX GUI:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <VBox> <children> <Label text="Hello world FXML"/> </children> </VBox>
This example defines a VBox containing a single Label as
child element. The VBox
component is a JavaFX layout component. The Label
just shows
a text in the GUI. Don't worry if you do not yet understand all the JavaFX components. You will once
you start playing with them all.
The first line in the FXML document is the standard first line of XML documents.
The following two lines are import statements. In FXML you need to import the classes you want to use. Both JavaFX classes and core Java classes used in FXML must be imported.
After the import statements you have the actual composition of the GUI. A VBox
component
is declared, and inside its children
property is declared a single Label
component.
The result is that the Label
instance will be added to the children
property
of the VBox
instance.
Loading an FXML File
In order to load an FXML file and create the JavaFX GUI components the file declares, you use the
FXMLLoader
(javafx.fxml.FXMLLoader
) class. Here is a full JavaFX FXML loading example
which loads an FXML file and returns the JavaFX GUI component declared in it:
import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.layout.VBox; import javafx.stage.Stage; import java.net.URL; public class FXMLExample extends Application{ public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { FXMLLoader loader = new FXMLLoader(); loader.setLocation(new URL("file:///C:/data/hello-world.fxml")); VBox vbox = loader.<VBox>load(); Scene scene = new Scene(vbox); primaryStage.setScene(scene); primaryStage.show(); } }
For this example to work, the FXML file must be located at C:\data\hello-world.fxml
. As you can
see, the location of the file is set via the setLocation()
method. The root GUI component
(the VBox
object) is obtained via the load()
method.
Importing Classes in FXML
In order to use a Java class in FXML, whether a JavaFX GUI component or a regular Java class, the class must be imported in the FXML file. FXML import statements look like this:
<?import javafx.scene.layout.VBox?>
This FXML import statement imports the class javafx.scene.layout.VBox
.
Creating Objects in FXML
FXML can create both JavaFX GUI objects as well as non-JavaFX objects. There are several ways to create objects in FXML. In the following sections we will see what these options are.
Creating Objects Via FXML Elements and No-arg Constructors
The easiest way to create objects in FXML is via an FXML element in an FXML file. The element names used in FXML are the same names as the Java class names without the package names. Once you have imported a class via an FXML import statement, you can use its name as an FXML element name.
In the following example the element names VBox
and Label
are valid because
these two classes are declared with import statements earlier in the FXML file:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <VBox> <children> <Label text="Hello world FXML"/> </children> </VBox>
To create objects using FXML elements like this requires that the class of the created object has a no-argument constructor.
Creating Objects via valueOf() Method
Another way to create objects in FXML is to call a static valueOf()
method in the class you want
to create the object of. The way to create objects via a valueOf()
method is to insert a
value
attribute in the FXML element. Here is an example:
<?xml version="1.0" encoding="UTF-8"?> <?import com.jenkov.javafx.MyClass?> <MyClass value="The Value"/>
Here is how the corresponding MyClass
needs to look for this to work:
public MyClass { public static MyClass valueOf(String value) { return new MyClass(value); } private String value = null; public MyClass(String value) { this.value = value; } }
Notice the static valueOf()
method which takes a Java String as
parameter. This method is called by the FXMLLoader
when it sees the MyClass
element
in the FXML file. The object returned by the valueOf()
method is what is inserted into the
GUI composed in the FXML file. The above FXML doesn't contain any other elements than the MyClass
element, but it could.
Keep in mind that whatever object is returned by the valueOf()
method will be used in the object
graph (composed GUI). If the object returned is not an instance of the class containing the valueOf()
method, but an instance of some other class, then that object will still be used in the object graph. The element
name is used only to find the class containing the valueOf()
method (when the FXML element contains a
value
attribute).
Creating Objects Via Factory Methods
In a sense, a valueOf()
method is also a factory method that creates objects based on a
String parameter. But - you can also get the FXMLLoader
to call other factory methods than a
valueOf()
method.
To call another factory method to create an object, you need to insert an fx:factory
attribute.
The value of the fx:factory
attribute should be the name of the factory method to call.
Here is an example:
<?xml version="1.0" encoding="UTF-8"?> <?import com.jenkov.javafx.MyClass?> <MyClass fx:factory="instance"/>
The MyClass class should look like this for the above FXML example to work:
public MyClass { public static MyClass instance() { return new MyClass(); } }
Notice the instance()
method. This method is referenced from the fx:factory
attribute in the FXML snippet above.
Note, that the factory method must be a no-argument method to call it from a fx:factory
attribute.
Properties in FXML
Some JavaFX objects have properties. In fact, most of them do. You can set the values of properties in two ways. The first way is to use an XML attribute to set the property value. The second way is to use a nested XML element to set the property value.
To understand how to set properties in FXML elements better, let us look at an example:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <VBox spacing="20"> <children> <Label text="Line 1"/> <Label text="Line 2"/> </children> </VBox>
This example shows 3 property examples. The first example is the spacing
attribute in the
VBox
element. The value set in the spacing
attribute is passed as parameter
to the setSpacing()
method of the VBox
object created based on the VBox
element.
The second example is the children
element nested inside the VBox
element.
This element corresponds to the getChildren()
method of the VBox
class.
The elements nested inside the children
element will be converted to JavaFX components that
are are added to the collection obtained from the getChildren()
method of the VBox
object represented by the parent VBox
element.
The third example are the text
attributes of the two Label
elements nested inside
the children
. The values of the text
attributes will be passed as parameters
to the setText()
property of the Label
objects created by the Label
elements.
Property Name Matching
FXML considers "properties" to be member variables accessed via getters and setters. E.g.
getText()
and setText()
.
As you can see from the example in the previous section the property names of JavaFX classes are matched to the attribute and element names by:
- Remove any get/set in the property name.
- Convert first remaining character of property name to lowercase.
Thus, the getter method getChildren
will first be reduced to Children
and then to children
. Similarly, the setter method setText
will be reduced
to Text
and then to text
.
Default Properties
A JavaFX component can have a default property. That means, that if a FXML element contains children which are not nested inside a property element, then it is assumed that the children are belonging to the default property.
Let us look at an example. The VBox
class has the children
property as default
property. That means that we can leave out the children
element. Thus, this FXML:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <VBox spacing="20"> <children> <Label text="Line 1"/> <Label text="Line 2"/> </children> </VBox>
can be shortened to:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <VBox spacing="20"> <Label text="Line 1"/> <Label text="Line 2"/> </VBox>
The two Label
elements are then assumed to belong to the default property of VBox
,
which is the children
property.
A default property is marked with the JavaFX annotation @DefaultProperty(value="propertyName")
where the value is the name of the property that should be the default property. For instance, the
@DefaultProperty(value="children")
declaration would make the children
property
the default property.
FXML Namespace
FXML has a namespace you can set on the root element of your FXML files. The FXML namespace is needed for
some FXML attributes like the fx:id
attribute (see the next section in this FXML tutorial).
Setting the FXML namespace on the root element of an FXML file looks like this:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <VBox xmlns:fx="http://javafx.com/fxml"> </VBox>
The FXML namespace is declared by the attribute declaration xmlns:fx="http://javafx.com/fxml"
.
FXML Element IDs
You can assign IDs to FXML elements. These IDs can be used to reference the FXML elements elsewhere in
the FXML file. Specifying an ID for an FXML element is done via the id
attribute from the
FXML namespace. Here is an example of specifying and ID for an FXML element:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <VBox xmlns:fx="http://javafx.com/fxml"> <Label fx:id="label1" text="Line 1"/> </VBox>
Notice the attribute declaration fx:id="label1"
in the Label
element. This attribute
declares the ID of that Label
element. Now this specific Label
element can be
referenced via the ID label1
elsewhere in the FXML document. For instance, this ID can be used
to reference the FXML element from CSS. You will see examples of referencing FXML elements by their ID
later in this FXML tutorial.
FXML Event Handlers
It is possible to set event handlers on JavaFX objects from inside the FXML file that defines the JavaFX objects. You might prefer to set advanced event handlers from within Java code, but for simple event handlers setting them from within FXML might be fine.
In order to define an event handler you need to use a script
element. Here is how an FXML
script element looks:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <VBox xmlns:fx="http://javafx.com/fxml"> <Label fx:id="label1" text="Button not clicked"/> <Button fx:id="button1" text="Click me!" onAction="reactToClick()"/> <fx:script> function reactToClick() { label1.setText("Button clicked"); } </fx:script> </VBox>
This example shows two interesting FXML concepts. The first concept is adding an event listener to a JavaFX
component from within FXML. The Button
element declares an event listener via its
onAction
attribute. The attribute value declares a call to the reactToClick()
function
which is defined in the script
element further down the FXML file.
The second concept is the reference of a JavaFX component via its ID from within the FXML file. Inside the
reactToClick()
method declared in the script
element, the Label
element
is referenced via its ID label1
, via this statement:
label1.setText("Button clicked");
The onAction
event listener attribute corresponds to the onAction
event of the
Button
component. You can set this event listener via Java code too, via the Button
setOnAction()
method. You can set listeners for other events in FXML too, by matching their
event listener methods from the corresponding JavaFX component with an FXML attribute, using the same
name matching rules as for other properties (see earlier section on property name matching).
FXML CSS Styling
It is possible to style the JavaFX components declared inside an FXML file. You can do so by embedding a
style
element inside the FXML element. Here is an example of CSS styling a JavaFX button in an
FXML file:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Button?> <VBox xmlns:fx="http://javafx.com/fxml"> <Button text="Click me!"/ onAction="reactToClick()"> <style> -fx-padding: 10; -fx-border-width: 3; </style> </Button> </VBox>
This example sets the -fx-padding
CSS property to 10, and the -fx-border-width
property
to 3. Since the style
element is nested inside the button
element, these CSS styles will
be applied to that button
element.
FXML Controller Classes
You can set a controller class for an FXML document. An FXML controller class can bind the GUI components declared in the FXML file together, making the controller object act as a mediator (design pattern).
There are two ways to set a controller for an FXML file. The first way to set a controller is to specify
it inside the FXML file. The second way is to set an instance of the controller class on the
FXMLLoader
instance used to load the FXML document. This JavaFX FXML tutorial will show both
options in the following sections.
Specifying Controller Class in FXML
The controller class is specified in the root element of the FXML file using the fx:controller
attribute. Here is an example of specifying a controller in FXML:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Button?> <VBox xmlns:fx="http://javafx.com/fxml" fx:controller="com.jenkov.javafx.MyFxmlController" > <Button text="Click me!"/ onAction="reactToClick()"> </Button> </VBox>
Notice the fx:controller
attribute in the root element (the VBox
element).
This attribute contains the name of the controller class. An instance of this class is created when the
FXML file is loaded. For this to work, the controller class must have a no-argument constructor.
Setting a Controller Instance on the FXMLLoader
When setting a controller instance on an FXMLLoader
you must first create an instance of
the controller class, and then set that instance on the FXMLLoader
. Here is an example of setting
a controller instance on an FXMLLoader
instance:
MyFxmlController controller = new MyFxmlController(); FXMLLoader loader = new FXMLLoader(); loader.setController(controller);
Binding JavaFX Components to Controller Fields
You can bind the JavaFX components in the FXML file to fields in the controller class. To bind a JavaFX component
to a field in the controller class, you need to give the FXML element for the JavaFX component an fx:id
attribute which has the name of the controller field to bind it to as value. Here is an example controller class:
public class MyFxmlController { public Label label1 = null; }
And here is the FXML file with a Label
element bound to the label1
field of the
controller class:
<VBox xmlns:fx="http://javafx.com/fxml" > <Label fx:id="label1" text="Line 1"/> </VBox>
Notice how the value of the fx:id
attribute has the value label1
which is the
same as the field name in the controller class to which it should be bound.
Referencing Methods in the Controller
It is possible to reference methods in the controller instance from FXML. For instance, you can bind the events of a JavaFX GUI component to methods of the controller. Here is an example of binding an event of a JavaFX component to a method in the controller:
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="com.jenkov.javafx.MyFxmlController" spacing="20"> <children> <Label fx:id="label1" text="Line 1"/> <Label fx:id="label2" text="Line 2"/> <Button fx:id="button1" text="Click me!" onAction="#buttonClicked"/> </children> </VBox>
This example binds the onAction
event of the Button
to the method buttonClicked
in the controller class. Here is how the controller class must look to enable the event binding:
import javafx.event.Event; import javafx.fxml.FXML; import javafx.scene.control.Label; public class MyFxmlController { @FXML public void buttonClicked(Event e){ System.out.println("Button clicked"); } }
Notice the @FXML
annotation above the buttonClicked
method. This annotation marks
the method as a target for binding for FXML. Notice also that the name buttonClicked
is referenced
in the FXML file.
Obtaining the Controller Instance From the FXMLLoader
Once the FXMLLoader
instance has loaded the FXML document, you can obtain a reference to the
controller instance via the FXMLLoader
getController()
method. Here is an example:
MyFxmlController controllerRef = loader.getController();
Tweet | |
Jakob Jenkov |