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
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 reproduce the problem:
* 1) run TestApp
* 2) Click "+"
* 3) type into the name field for the added column.
* The cell that you just typed into *and the cell below* both get a red border.
*/
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)
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 = 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 = textfield(model.title)
}
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