Created
September 25, 2019 12:52
-
-
Save stephenbmfj/385984406d5281e088dde59287d8b310 to your computer and use it in GitHub Desktop.
TornadoFX tableview - always editable, dirty indicator, add/remove rows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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