Search code examples
tornadofx

TornadoFX - how to fix ListView with a custom cell factory not updating properly when items are deleted?


I have a ListView displaying custom objects from my domain model, and if I use a custom cell factory to display the objects' properties in each row of the list, I get strange behaviour when I delete items. If the item is not the last in the list, the deleted item remains visible and the last item disappears. However, the item has been removed from the backing list as expected, and attempting to delete the phantom object has no further effect.

The display seems not to be refreshing properly, because after some arbitrary resizing of the window, the list eventually refreshes to its expected values. I've tried calling refresh() on the ListView manually but it has no noticeable effect.

Removing my custom cell factory fixes the problem, and I've seen other posts that have had a similar problem using standard JavaFX (ListView using custom cell factory doesn't update after items deleted) where the problem is fixed by changing the implementation of updateItem(Object item, boolean empty), but I can't work out how to do that in TornadoFX.

Here's an example that demonstrates the update issue (but not the phantom item, that only happens if the delete button is part of the custom cell):

package example

import javafx.scene.control.ListView
import tornadofx.*

data class DomainClass(val name: String, val flag1: Boolean, val flag2: Boolean, val info: String)

class UpdateIssue : App(UpdateIssueView::class)

class UpdateIssueView : View() {

    val listSource = mutableListOf(
            DomainClass("object1", true, false, "more info"),
            DomainClass("object2", false, true, "even more info"),
            DomainClass("object3", false, false, "all the info")
    ).observable()
    var lst: ListView<DomainClass> by singleAssign()

    override val root = vbox {
        lst = listview(listSource) {
            cellFormat {
                graphic = cache {
                    hbox {
                        textfield(it.name)
                        combobox<Boolean> {
                            selectionModel.select(it.flag1)
                        }
                        combobox<Boolean> {
                            selectionModel.select(it.flag2)
                        }
                        textfield(it.info)
                    }
                }
            }
        }
        button("delete") {
            action {
                listSource.remove(lst.selectedItem)
            }
        }
    }
}

Any help greatly appreciated!


Solution

  • The suggestion from @Edvin Syse to remove the cache block fixed this for me (although note that he also said a more performant fix would be to implement a ListCellFragment, which I haven't done here):

        ....
        lst = listview(listSource) {
            cellFormat {
                graphic = hbox {
                    textfield(it.name)
                    combobox<Boolean> {
                        selectionModel.select(it.flag1)
                    }
                    combobox<Boolean> {
                        selectionModel.select(it.flag2)
                    }
                    textfield(it.info)
                }
            }
        }