JavaFX with Scala

July 30, 2015
JavaFX with Scala

Good morning,

One of the big advantages of Scala is its interoperability with Java. Wouldn't it be nice to write JavaFX applications using Scala? Well, you can (otherwise this article would be short, indeed). In this article, I will show what to do to bring JavaFX and Scala to a happy union.

The goal is to write a small JavaFX application, whose UI is defined in FXML-files and can thus be created using SceneBuilder. Additionally, we want to use the comfort of dependency injection via the @FXML attribute, which thanks to the interoperability of Scala and Java is not hard to do at all. To keep things simple, we take a view first approach, i.e. the view defines the controller to use and the FXMLLoader does all the initialising.

First, we need to get JavaFX to load. Let's start just like we would in Java, by creating a class inheriting from Application.

// File: eu.derprogrammierer.jfx.FxApp.scala
package eu.derprogrammierer.jfx

import javafx.application.Application
import javafx.stage.Stage

class FxApp extends Application {
override def start(primaryStage: Stage): Unit = {
primaryStage.setTitle("Fx App")
primaryStage.show()
}
}

In this example, we set the window title and subsequently show it. Later we will also fill the window with some content.

In your average JavaFX application, there is also a static main() function which in turn calls the static launch() function on FxApp. However, since Scala does not implement static functions, we will have to use a companion object for the class FxApp instead. Companion objects do not automatically implement functions of the base class, though. Which is why the following port of the typical Java code will not work.

object FxApp extends Application {
def main (args: Array[String]): Unit = {
launch(args:_*) // Cannot resolve symbol launch
}
}

Luckily Application provides a more generic function for us to use, such that we can call launch on the Application base class and give it the type of our FxApp to specify the actual instance type.

// File: eu.derprogrammierer.jfx.FxApp.scala

...

object FxApp {
def main (args: Array[String]): Unit = {
Application.launch(classOf[FxApp], args:_*)
}
}

The application can now be started and displays an empty JavaFX(!) window. This is nice and all, but we really want to put some content into this empty window, and there is still that thing about FXML, right?

Well, before we can dive into FXML we require a controller to be used in our view. A basic controller implementation is very similar to what you would do in Java.

// File: eu.derprogrammierer.jfx.view.MainViewController.scala
package eu.derprogrammierer.jfx.view

import javafx.fxml.FXML
import javafx.scene.control.{Label}

class MainViewController {
@FXML private var testLabel: Label = _

def initialize(): Unit = {
testLabel.setText("Initialized!")
}

def handleTest(): Unit = {
testLabel.setText("Test successful!")
}
}

As you can see, our view will contain a single label named "testLabel". Its text is changed in the initialize()-method. Also, there is a handler handleTest() which changes the text. This handler is supposed to be executed when clicking a button.

Note that IntelliJ IDEA recommends changing testLabel into a "val", which does however break the JavaFX injection. It is also not possible to drop the @FXML attribute on public fields like you could do in Java. This is due to the way Scala handles public fields. A public field in Scala is actually a getter and setter method wrapped around a private field. This is not noticable for normal programming. However, when using reflection to access fields, as is done for FXML, this can have some major implications!

Main.fxml in SceneBuilder

Our controller is now in working order, so we can finally start to create a view (FXML, yessss!). To do that, we add a file "MainView.fxml" to the package "view". In SceneBuilder we drag an AnchorPane into the empty window. We place a button and a label in the middle of the pane.

Action handleTestLabel fxid

The button gets the action "handleTest" and the label gets the fx:id "testLabel" (both of which should seem familiar from the controller).

MainView Controller Class

Also, our view wants to have a controller, so we enter the complete name of our controller into the field "Controller class".

What's left to do is to load the view in the class FxApp. For this, we change the function start() as follows:

override def start(primaryStage: Stage): Unit = {
primaryStage.setTitle("Fx App")

val fxmlLoader = new FXMLLoader(getClass.getResource("view/MainView.fxml"))
val mainViewRoot: Parent = fxmlLoader.load()

val scene = new Scene(mainViewRoot)
primaryStage.setScene(scene)
primaryStage.show() }

This requires some additional imports, too:

import javafx.fxml.FXMLLoader
import javafx.scene.{Scene, Parent}

You can now run the program again and you will find the label correctly initialized. Clicking the button changes the text of the label to "Test successful!". A successful test indeed!

You now have a fully usable JavaScalaFX application. I will leave it up to the reader to make a "Hello World!" example from that.