-
Notifications
You must be signed in to change notification settings - Fork 272
FXML
Wiki ▸ Documentation ▸ FXML
The type safe builders of TornadoFX provide a fast, easy and declarative way to construct your UI and is for most cases your best choice. JavaFX does however also support an XML-based language that can do the same thing.
With FXML it is very easy to separate your UI logic code from the code that constructs the UI. While this separation is just as achievable with the type safe builders by utilising the MVP pattern or similar, some programmers find that by using FXML they are forced to maintain this separation and prefer it for exactly that reason.
The FXML files can also be processed by Scene Builder, a visual layout tool that lets you build your user interface with drag and drop and immediately see the result in a WYSIWYG editor.
If you're converting an existing JavaFX application to TornadoFX, chances are your UI is already constructed with FXML, and you might want to keep it that way instead of converting the code to use the type safe builders.
If you are unfamiliar with FXML and want to stick with the builders, feel free to skip this chapter or take a look at the official FXML documentation to learn more about it.
As you have seen earlier, the root
property of a View
represents the hierarchy of Nodes that build up the user interface. When you work with FXML, you don't instantiate this root node directly, but instead asks TornadoFX to load it from a corresponding FXML file. By default, TornadoFX will look for a file with the same name as your view with the .fxml
file ending, in the same package as your View
class. You can also override the FXML location with a parameter if you want to put all your FXML files in a single folder or arrange them some other way that does not directly correspond to your View location.
Let's create a basic user interface that presents you with a label and a button. We will add functionality to this view so that when the button is clicked, the label will update with the number of times the button has been clicked.
Create a file named CounterView.fxml
with the following content:
<BorderPane xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
<padding>
<Insets top="20" right="20" bottom="20" left="20"/>
</padding>
<center>
<VBox alignment="CENTER" spacing="10">
<Label text="0">
<font>
<Font size="20"/>
</font>
</Label>
<Button text="Click to increment" />
</VBox>
</center>
</BorderPane>
If you load this file in Scene Builder you will see the following result:
Now we need to load this Node hierarchy into the root
node of our View. We define the following View class:
class CounterView : View() {
override val root : BorderPane by fxml()
}
The fxml()
delegate takes care of loading the corresponding CounterView.fxml
into the root
property for us. If we placed CounterView.fxml
in an alternate location, let's say /views/
, we would add a parameter:
class CounterView : View() {
override val root : BorderPane by fxml("/views/CounterView.fxml")
}
This is enough to show the UI, but it has no functionality. We need to define a variable that can represent the number of times the button has been clicked. We add a variable called counter
and define a function that will increment it's value:
class CounterView : View() {
override val root : BorderPane by fxml()
val counter = SimpleIntegerProperty()
fun increment() {
counter.value += 1
}
}
We want the increment()
function to be called whenever the button is clicked. Back in our FXML file, we add the onAction
attribute to the button:
<Button text="Click to increment" onAction="#increment"/>
Since the FXML file automatically gets bound to our View, we can reference functions via the #functionName
syntax. Note that we do not add parenthesis to the function call, and you cannot pass parameters directly. You can however add a parameter of type javafx.event.ActionEvent
to the increment
function if you want inspect the source Node of the action or check what kind of action triggered the button. For this example we don't need it, so we leave the increment
function without parameters.
The next thing we want to do is to get a hold of the Label from the FXML file so that we can bind the counter
value to the text
property of the Label. We need an identifier for the Label, so in our FXML file we add the fx:id
attribute to it:
<Label fx:id="counterLabel">
Now we can inject this Label into our View class:
val counterLabel : Label by fxid()
This tells TornadoFX to look for a Node with the fx:id
property set to the same name as the property we defined (counterLabel). It is also possible to use another property name in the View and add a name parameter to the fxid
delegate:
val myLabel : Label by fxid("counterLabel")
Now that we have a hold of the Label, we can use the binding shortcuts of TornadoFX to bind the counter value to the text property of the Label. The whole View now looks like this:
class CounterView : View() {
override val root : BorderPane by fxml()
val counter = SimpleIntegerProperty()
val counterLabel: Label by fxid()
init {
counterLabel.bind(counter)
}
fun increment() {
counter.value += 1
}
}
Our app is now complete. Every time the button is clicked, the label will reflect the new number of total clicks.
JavaFX has strong support for multi language UI's.
The Scene Builder tool was created by Oracle/Sun but is now maintained by Gluon, an innovative company that invests heavily in JavaFX technology, especially for the mobile market.
Next: Application Startup