I have a simple tornadoFX program that generates some circles in random locations on the screen. However, none of the circles get drawn. I've added some debug code to print a line when a circle is drawn, and it only prints once.
I would expect circles to appear at 100ms intervals, as well as when I click the "Add actor" button.
private const val WINDOW_HEIGHT = 600
private const val WINDOW_WIDTH = 1024
fun main(args: Array<String>) {
Application.launch(MainApp::class.java, *args)
}
class MainApp : App(WorldView::class, Stylesheet::class)
data class Actor(val x: Double, val y: Double)
class WorldView: View("Actor Simulator") {
override val root = VBox()
private val actors = ArrayList<Actor>(0)
init {
tornadofx.runAsync {
(0..100).forEach {
val x = ThreadLocalRandom.current().nextDouble(0.0, WINDOW_WIDTH.toDouble())
val y = ThreadLocalRandom.current().nextDouble(0.0, WINDOW_HEIGHT.toDouble())
actors.add(Actor(x, y))
Thread.sleep(100)
}
}
}
init {
with(root) {
stackpane {
group {
bindChildren(actors.observable()) {
circle {
centerX = it.x
centerY = it.y
radius = 10.0
also {
println("drew circle")
}
}
}
}
button("Add actor") {
action {
actors.add(Actor(0.0, 0.0))
}
}
}
}
}
}
Oddly, if I put a breakpoint during the circle draw code, circles will draw and the debug line will print.
Some observations:
Calling someList.observable()
will create an observable list backed by the underlying list, but mutations on the underlying list will not emit events. You should instead initialize actors
as an observable list right away.
Access to an observable list must happen on the UI thread, so you need to wrap mutation calls in runLater
.
For people trying to run your example - you didn't include a stylesheet, but references one in your App
subclass, so the IDEA will most probably import the TornadoFX Stylesheet
class. This will not end well :)
The also
call has no effect, so I removed it.
I updated your code to best practices here and there, for example with regards to how to create the root node :)
Updated example taking these points into account looks like this:
private const val WINDOW_HEIGHT = 600.0
private const val WINDOW_WIDTH = 1024.0
class MainApp : App(WorldView::class)
data class Actor(val x: Double, val y: Double)
class WorldView : View("Actor Simulator") {
private val actors = FXCollections.observableArrayList<Actor>()
override fun onDock() {
runAsync {
(0..100).forEach {
val x = ThreadLocalRandom.current().nextDouble(0.0, WINDOW_WIDTH.toDouble())
val y = ThreadLocalRandom.current().nextDouble(0.0, WINDOW_HEIGHT.toDouble())
runLater {
actors.add(Actor(x, y))
}
Thread.sleep(100)
}
}
}
override val root = stackpane {
setPrefSize(WINDOW_WIDTH, WINDOW_HEIGHT)
group {
bindChildren(actors) {
circle {
centerX = it.x
centerY = it.y
radius = 10.0
println("drew circle")
}
}
}
button("Add actor") {
action {
actors.add(Actor(0.0, 0.0))
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With