I am following TornadoFX Guide from here, trying to run the sample wizard: Wizard
I am having the following trouble, see item 3):
1) When I press Cancel
the values are rolled back to empty values on
next open
2) When I press Finish
the values are still in the wizard on next open
3) When I now press Cancel
changes are not rolled back to 2) but to
1), i. e. empty fields
How do I get different Cancel
behaviour: rolling back the CustomerModel to the last valid state?
This is my updated CustomerWizard.kt
:
package com.example.demo.view
import com.example.demo.app.Customer
import com.example.demo.app.CustomerModel
import tornadofx.*
class CustomerWizard : Wizard("Create customer", "Provide customer information") {
val customer: CustomerModel by inject()
override val canGoNext = currentPageComplete
override val canFinish = allPagesComplete
override fun onCancel() {
super.onCancel()
customer.rollback()
}
override fun onSave() {
super.onSave()
customer.commit()
println("customer.name=" + customer.name)
println("customer.type=" + customer.type)
println("customer.zip=" + customer.zip)
println("customer.city=" + customer.city)
}
init {
graphic = resources.imageview("/graphics/customer.png")
add(BasicData::class)
add(AddressInput::class)
}
}
class BasicData : View("Basic Data") {
val customer: CustomerModel by inject()
override val complete = customer.valid(customer.name)
override val root = form {
fieldset(title) {
field("Type") {
combobox(customer.type, Customer.Type.values().toList())
}
field("Name") {
textfield(customer.name).required()
}
}
}
}
class AddressInput : View("Address") {
val customer: CustomerModel by inject()
override val complete = customer.valid(customer.zip, customer.city)
override val root = form {
fieldset(title) {
field("Zip/City") {
textfield(customer.zip) {
prefColumnCount = 5
required()
}
textfield(customer.city).required()
}
}
}
}
This is my CustomerModel.kt
:
package com.example.demo.app
import tornadofx.*
class CustomerModel(customer: Customer? = null) : ItemViewModel<Customer>(customer) {
val name = bind(Customer::nameProperty, autocommit = true)
val zip = bind(Customer::zipProperty, autocommit = true)
val city = bind(Customer::cityProperty, autocommit = true)
val type = bind(Customer::typeProperty, autocommit = true)
}
This is my MainView.kt
:
package com.example.demo.view
import com.example.demo.app.Customer
import com.example.demo.app.CustomerModel
import com.example.demo.app.Styles
import javafx.geometry.Pos
import javafx.scene.layout.Priority
import javafx.scene.paint.Color
import tornadofx.*
class MainView : View("Hello TornadoFX") {
private val myCustomer: Customer? = Customer("test", 12345, "", Customer.Type.Private)
override val root = drawer {
item("Generate & sign", expanded = true) {
button("Add Customer").action {
find<CustomerWizard>(Scope(CustomerModel(myCustomer))).openModal()
}
}
item("Verify") {
borderpane {
top = label("TOP") {
useMaxWidth = true
alignment = Pos.CENTER
style {
backgroundColor += Color.RED
}
}
bottom = label("BOTTOM") {
useMaxWidth = true
alignment = Pos.CENTER
style {
backgroundColor += Color.BLUE
}
}
left = label("LEFT") {
useMaxWidth = true
useMaxHeight = true
style {
backgroundColor += Color.GREEN
}
}
right = label("RIGHT") {
useMaxWidth = true
useMaxHeight = true
style {
backgroundColor += Color.PURPLE
}
}
center = label("CENTER") {
useMaxWidth = true
useMaxHeight = true
alignment = Pos.CENTER
style {
backgroundColor += Color.YELLOW
}
}
}
}
item("Sign next") {
borderpane {
top = label("TOP") {
useMaxWidth = true
alignment = Pos.CENTER
style {
backgroundColor += Color.RED
}
}
bottom = label("BOTTOM") {
useMaxWidth = true
alignment = Pos.CENTER
style {
backgroundColor += Color.BLUE
}
}
left = label("LEFT") {
useMaxWidth = true
useMaxHeight = true
style {
backgroundColor += Color.GREEN
}
}
right = label("RIGHT") {
useMaxWidth = true
useMaxHeight = true
style {
backgroundColor += Color.PURPLE
}
}
center = label("CENTER") {
useMaxWidth = true
useMaxHeight = true
alignment = Pos.CENTER
style {
backgroundColor += Color.YELLOW
}
}
}
}
}
//class Link(val name: String, val uri: String)
//class Person(val name: String, val nick: String)
// Sample data variables left out (iPhoneUserAgent, TornadoFXScreencastsURI, people and links)
}
When you call rollback()
on an ItemViewModel
, it will roll back to the data found in the underlying item
, or blank values if you never backed your ItemViewModel
with an item.
In your case, that means you need to assign a Customer
to the item
property of your CustomerModel
. After you have done that, you can call rollback()
and the CustomerModel
will show the state from the Customer
it is backing.
If you get the same state when you reopen the Wizard, that means that you have reopened the exact same Wizard instance. A Wizard extends View
, which makes it a singleton within it's scope, so if you just call find()
to locate the Wizard without specifying a scope, the second attempt will get you the same instance as the first.
You didn't post your Wizard init code, but generally you should create a new scope for the Wizard if you want to avoid this. If you want the wizard to edit a specific customer instance, you should do:
val model = CustomermerModel()
model.item = myCustomer
find<CustomerWizard>(Scope(model).openModal()
It is for this reason normal to let the view model accept an instance in the constructor, which you pass on to the ItemViewModel
constructor, so that the item is assigned for you automatically:
class CustomerModel(customer: Customer? = null) : ItemViewModel<Customer>(customer)
Not that I made sure to allow a no-args constructor for the CustomerModel, to support inject in cases where there is no CustomerModel in scope already. In that case a new CustomerModel (not supporting any customer item) will be created, so this situation requires a no-args constructor.
With that in place, the wizard init code becomes:
find<CustomerWizard>(Scope(CustomerModel(myCustomer)).openModal()
Hope this helps.