I’m playing around with JavaFX and Kotlin discovered a weird behaviour with javafx properties.
I have a property selected
which is bound to a LIstViews’ selectedItemProperty, then I have another property todo
who is bound to selected
. Weird thing is whenever selected
’s value change (I click on an item in the UI) todo
is never updated, listeners are not invoked either. Unless I do something like:
todo.bind(component.selected)
// WTF?
var once = false
component.selected.addListener({ obs, old, selected ->
if (!once) {
todo // why?
once = true
}
})
Probably it’s worth mentioning that my code is littered with lazy
.
Below is the complete source containing the snippet above. Full source for the test project is available here.
package io.polymorphicpanda.zerofx.sample.view
import io.polymorphicpanda.zerofx.sample.component.TodoAppComponent
import io.polymorphicpanda.zerofx.sample.component.TodoDetailsComponent
import io.polymorphicpanda.zerofx.sample.domain.Todo
import io.polymorphicpanda.zerofx.view.View
import io.polymorphicpanda.zerofx.view.helpers.*
import javafx.event.EventHandler
import javafx.geometry.Insets
import javafx.geometry.Pos
import javafx.scene.control.*
import javafx.scene.layout.*
import javafx.util.Callback
class TodoAppView(component: TodoAppComponent): View<TodoAppComponent>(component) {
override val root by lazy(LazyThreadSafetyMode.NONE) {
BorderPane().apply {
styleClass("main-component")
left = VBox().apply {
margin(this, Insets(5.0))
alignment = Pos.CENTER_LEFT
spacing = 5.0
isFillWidth = true
children {
+ HBox().apply {
isFillHeight = true
spacing = 5.0
children {
+ TextField().apply {
textProperty().bindBidirectional(component.newTodoDescription)
promptText = "Create new todo"
hgrow(this, Priority.ALWAYS)
}
+ Button("Add"). apply {
hgrow(this, Priority.ALWAYS)
disableProperty().bind(component.newTodoDescription.isEmpty)
onAction = EventHandler {
component.addTodo()
}
}
}
}
+ Label("Todo List")
+ createListView().apply {
vgrow(this, Priority.ALWAYS)
}
}
}
center = StackPane().apply {
children {
+ create(TodoDetailsComponent::class).apply {
todo.bind(component.selected)
// we need to do this?
var once = false
component.selected.addListener({ obs, old, selected ->
if (!once) {
todo
once = true
}
})
}
}
}
}
}
private fun createListView(): ListView<Todo> {
return ListView<Todo>().apply {
styleClass("todos")
isFocusTraversable = false
cellFactory = Callback {
object: ListCell<Todo>() {
override fun updateItem(item: Todo?, empty: Boolean) {
super.updateItem(item, empty)
if (!empty && item != null) {
text = item.description
}
}
}
}
selectionModel.apply {
component.selected.bind(this.selectedItemProperty())
}
itemsProperty().bind(component.todos)
}
}
}