Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my tornadoFX ObservableList not receive updates?

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.

like image 497
Malcolm Crum Avatar asked Jan 28 '23 01:01

Malcolm Crum


1 Answers

Some observations:

  1. 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.

  2. Access to an observable list must happen on the UI thread, so you need to wrap mutation calls in runLater.

  3. 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 :)

  4. The also call has no effect, so I removed it.

  5. 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))
            }
        }
    }
}
like image 86
Edvin Syse Avatar answered Jan 31 '23 11:01

Edvin Syse