What I am trying to accomplish is: having a ScalaFX application with some nice ordered object
s called Buttons
, Labels
, Checkboxes
and so on to keep everything nice and in order.
Here a little example to show what I mean:
package ButtonsAndLabel
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.control.{ Button, Label }
import scalafx.event.ActionEvent
object Main extends JFXApp {
stage = new JFXApp.PrimaryStage {
title = "Test-Program"
scene = new Scene(300, 200) {
val label = new Label("Nothing happened yet") {
layoutX = 20
layoutY = 20
}
val button1 = new Button("Button 1") {
layoutX = 20
layoutY = 50
onAction = (e: ActionEvent) => {
label.text = "B1 klicked"
}
}
val button2 = new Button("Button 2") {
layoutX = 20
layoutY = 80
onAction = (e: ActionEvent) => {
label.text = "B2 klicked"
}
}
content = List(label, button1, button2)
}
}
}
This code shows a window with a label and two buttons, and the buttons change the text of the label.
That works fine.
But when my code grows with a lot more controls, things get messy.
That's why I tried to transfer the controls into other object
s (in different files). I've put the label into an object called Labels
:
package ButtonsAndLabel
import scalafx.scene.control.Label
import scalafx.event.ActionEvent
object Labels {
val label = new Label("Nothing happened yet") {
layoutX = 20
layoutY = 20
}
}
when I import this into the main-file with
import Labels.label
everything works fine.
But then I try to put the buttons into a Buttons
object:
package ButtonsAndLabel
import scalafx.scene.control.Button
import scalafx.event.ActionEvent
import Labels.label
object Buttons {
val button1 = new Button("Button 1") {
layoutX = 20
layoutY = 50
onAction = (e: ActionEvent) => {
label.text = "B1 klicked"
}
}
val button2 = new Button("Button 2") {
layoutX = 20
layoutY = 80
onAction = (e: ActionEvent) => {
label.text = "B2 klicked"
}
}
}
this brings the error message when I try to compile:
[error] found : scalafx.event.ActionEvent => Unit [error] required: javafx.event.EventHandler[javafx.event.ActionEvent] [error] onAction = (e: ActionEvent) => {
and now I am stuck, as I don't know any Java.
Does anybody know if it is even possible what I am trying to do?
So far I have not found anything about this on the net. The problem doesn't keep me from writing the program I want, but the last application I wrote was a real mess with all the controls in one file.
Am I overlooking something obvious here?
Any help would be really appreciated.
Firstly, your approach is perfectly OK.
The error you're seeing actually has nothing to do with Java—it's output by the Scala compiler! All it's saying is that it has been supplied one type of element (in this case, a function that takes a scalafx.event.ActionEvent
and that returns Unit
) when it was expecting another type of element (a javafx.event.EventHandler[javafx.event.ActionEvent]
instance, in this case).
ScalaFX is just a set of Scala-friendly wrappers for the JavaFX library; without the implicit
conversion functions that convert between the two sets of elements, the Scala compiler will complain about finding ScalaFX elements when it needs JavaFX elements, and vice versa.
The solution is to ensure that the following import
is added to each of your ScalaFX source files:
import scalafx.Includes._
(You have this at the top of your main source file, but not the others.)
This will ensure that your ScalaFX ActionEvent
handler is converted into the JavaFX equivalent, thereby making your life a little easier.
This is a very common type of error with ScalaFX, which is nearly always fixed by specifying the above import
. (If the import
doesn't fix your problem, then you will typically have a genuine case of type confusion, in which you just plain used the wrong type of object.)
So, here's what I think your code needs to look like:
Main.scala
:
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.scene.Scene
import buttonsandlabel._
object Main extends JFXApp {
stage = new JFXApp.PrimaryStage {
title = "Test-Program"
scene = new Scene(300, 200) {
content = List(Labels.label, Buttons.button1, Buttons.button2)
}
}
}
buttonsandlabel/Labels.scala
:
package buttonsandlabel
import scalafx.Includes._
import scalafx.scene.control.Label
object Labels {
val label = new Label("Nothing happened yet") {
layoutX = 20
layoutY = 20
}
}
buttonsandlabel/Buttons.scala
:
package buttonsandlabel
import scalafx.Includes._
import scalafx.scene.control.Button
import scalafx.event.ActionEvent
import Labels.label
object Buttons {
val button1 = new Button("Button 1") {
layoutX = 20
layoutY = 50
onAction = (e: ActionEvent) => {
label.text = "B1 klicked"
}
}
val button2 = new Button("Button 2") {
layoutX = 20
layoutY = 80
onAction = (e: ActionEvent) => {
label.text = "B2 klicked"
}
}
}
(Note that package names, by convention, are typically all lowercase.)
One thing that you'll need to be aware of is the JavaFX Application Thread: all of your code that interacts with ScalaFX (or JavaFX) must execute on this thread. If you access ScalaFX/JavaFX from a different thread, you'll get an error exception. (This ensures that all such applications are thread-safe.) If you're unfamiliar with multi-threading, don't worry, ScalaFX initializes your application in such a way that this is fairly trivial. Usually, all that's needed is that your initialization code goes into your main application object's constructor (the object that extends JFXApp
).
When you start creating ScalaFX elements in other classes and objects, you need to take extra care. An object
is initialized when first referenced. If it is first referenced by code that is not executing on the JavaFX Application Thread, then you'll get thread error exceptions. One possible option is to put such code into def
or lazy val
members, so that they are only executed when referenced directly.
Alternatively, you may have to invoke your code via scalafx.application.Platform.runLater()
.
For more information on the JavaFX Application Thread, refer to the JavaFX documentation.