Skip to content
This repository has been archived by the owner on Sep 28, 2024. It is now read-only.
Edvin Syse edited this page Jun 23, 2016 · 11 revisions

WikiDocumentationFXML

(Work in progress)

FXML based user interfaces

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.

Reasons for considering FXML

Separation of concerns

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.

WYSIWYG Editor

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.

Compatibility with existing code

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.

How it works

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.

A simple example

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:

Load the FXML file with the fxml() delegate

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.

Access Nodes with the fxid delegate

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.

TODO: Localization

JavaFX has strong support for multi language UI's.

A note on Scene Builder

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

Clone this wiki locally