Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
TornadoFX tableview - always editable, dirty indicator, add/remove rows, swap input control
import javafx.beans.property.*
import javafx.collections.ObservableList
import javafx.scene.paint.Color
import tornadofx.*
/**
* Requirements:
*
* Show a table of Persons with 3 columns: name, title, and a "-" button to remove the person.
* There is a "+" button to add a person.
*
* Name and title must be always editable, changes made must propagate back to the model immediately.
*
* Make the border of a cell red if that cell's value is dirty.
*
* Currently has extra columns to show original values and current values.
*
* This works until you start adding/removing rows.
*
* To illustrate the problem: run, hit "+" 5 times, then start removing the rows "-" top first.
*/
class TestApp:App(TestView::class,TestStyles::class) {
companion object {
@JvmStatic fun main( args:Array<String> ) {
launch<TestApp>(args)
}
}
}
class Person( name:String, title:String ) {
val nameOrigProperty = SimpleStringProperty(name)
val nameOrig by nameOrigProperty
val nameProperty = SimpleStringProperty(name)
val nameDirty = SimpleBooleanProperty(false)
val titleOrigProperty = SimpleStringProperty(title)
val titleOrig by titleOrigProperty
val titleProperty = SimpleStringProperty(title)
val titleDirty = SimpleBooleanProperty(false)
override fun toString() = "Person \"${nameProperty.get()}\" - \"${titleProperty.get()}\""
init {
nameProperty.onChange {
nameDirty.set( nameProperty.get() != nameOrig )
}
titleProperty.onChange {
titleDirty.set( titleProperty.get() != titleOrig )
}
}
}
class TestView:View() {
val people:ObservableList<Person> =
mutableListOf(
Person("stephen","peon"),
Person("david","director"),
Person("xx","true")
).asObservable()
init {
title = "Test"
}
override val root =
tableview<Person> {
items = people
column("name",Person::nameProperty) {
cellFragment(PersonNameFragment::class)
}
column("name orig",Person::nameOrigProperty)
column("name view",Person::nameProperty)
column("title",Person::titleProperty) {
cellFragment(PersonTitleFragment::class)
}
column("title orig",Person::titleOrigProperty)
column("title view",Person::titleProperty)
column("",Person::nameProperty) {
graphic = button("+") {
action {
people.add(Person("",""))
}
}
cellFormat {
graphic = button("-") {
action {
people.remove(rowItem)
}
}
}
}
}
}
class PersonModel: ItemViewModel<Person>() {
val name = bind(property=Person::nameProperty,autocommit=true)
val title = bind(property=Person::titleProperty,autocommit=true)
fun useCheckbox() = name.value.contains("_")
fun useTextbox() = ! useCheckbox()
val showCheckboxProperty = SimpleBooleanProperty(false)
.apply {
this@PersonModel.name.onChange {
set(useCheckbox())
}
}
val showTextboxProperty = SimpleBooleanProperty(false)
.apply {
this@PersonModel.name.onChange {
set(useTextbox())
}
}
private fun calcClass( dirtyProperty:Property<Boolean> ):CssRule =
if ( dirtyProperty.value ) TestStyles.dirty else TestStyles.default
val nameClass:Property<CssRule> = SimpleObjectProperty<CssRule>()
.apply {
}
val titleClass = SimpleObjectProperty<CssRule>()
init {
name.onChange {
nameClass.value = calcClass( item.nameDirty )
}
title.onChange {
titleClass.value = calcClass( item.titleDirty )
}
}
}
class PersonNameFragment: TableCellFragment<Person,String>() {
val model = PersonModel().bindToRowItem(this)
init {
bindClass(model.nameClass)
}
override val root = hbox {
textfield(model.name)
}
}
fun <S,T> TableCellFragment<S,T>.bindClass(prop:Property<CssRule>) {
cellProperty
.onChange { cell ->
cell?.bindClass(prop)
}
}
class PersonTitleFragment: TableCellFragment<Person,String>() {
val model = PersonModel().bindToRowItem(this)
init {
bindClass(model.titleClass)
}
override val root =
stackpane {
textfield(model.title) {
removeWhen { model.showCheckboxProperty }
}
checkbox {
removeWhen { model.showTextboxProperty }
bind(model.title.toBooleanProperty())
}
}
}
fun Property<String>.toBooleanProperty():Property<Boolean> =
object:SimpleBooleanProperty() {
init {
value = this@toBooleanProperty.value?.toBoolean() ?: false
onChange { new ->
this@toBooleanProperty.value = new.toString()
}
this@toBooleanProperty.onChange { new ->
value = new?.toBoolean() ?: false
}
}
}
class TestStyles: Stylesheet() {
companion object {
val default by cssclass()
val dirty by cssclass()
}
init {
dirty {
borderColor = multi(box(Color.RED))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment