MVC 中的模型可以回滚到上一个有效状态吗?
Can model in MVC rollback to last valid state?
我从这里开始关注 TornadoFX 指南,尝试 运行 示例向导:
Wizard
我遇到了以下问题,请参阅第 3 项):
1) 当我按下 Cancel
时,值回滚到空值
下次开
2) 当我按下 Finish
时,这些值在下次打开时仍在向导中
3) 当我现在按下 Cancel
时,更改不会回滚到 2),而是回滚到
1), 一. e.空白字段
如何获得不同的 Cancel
行为:将 CustomerModel 回滚到上一个有效状态?
这是我的更新 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()
}
}
}
}
这是我的 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)
}
这是我的 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)
}
当您在 ItemViewModel
上调用 rollback()
时,它将回滚到在基础 item
中找到的数据,或者如果您从未支持 ItemViewModel
则返回空白值] 与一个项目。
在您的情况下,这意味着您需要将 Customer
分配给 CustomerModel
的 item
属性。完成后,您可以调用 rollback()
并且 CustomerModel
将显示它支持的 Customer
的状态。
如果您在重新打开向导时获得相同的状态,则意味着您重新打开了完全相同的向导实例。 Wizard 扩展了 View
,这使得它在其范围内成为单例,因此如果您只是调用 find()
来定位 Wizard 而未指定范围,则第二次尝试将获得与第一次相同的实例。
您没有 post 您的向导初始化代码,但如果您想避免这种情况,通常应该为向导创建一个新的范围。如果您希望向导编辑特定的客户实例,您应该这样做:
val model = CustomermerModel()
model.item = myCustomer
find<CustomerWizard>(Scope(model).openModal()
出于这个原因,让视图模型在构造函数中接受一个实例是正常的,您将其传递给 ItemViewModel
构造函数,以便自动为您分配项目:
class CustomerModel(customer: Customer? = null) : ItemViewModel<Customer>(customer)
并不是说我确保允许 CustomerModel 的无参数构造函数,以在范围内已经没有 CustomerModel 的情况下支持注入。在这种情况下,将创建一个新的 CustomerModel(不支持任何客户项目),因此这种情况需要一个无参数构造函数。
有了它,向导初始化代码就变成了:
find<CustomerWizard>(Scope(CustomerModel(myCustomer)).openModal()
希望这对您有所帮助。
我从这里开始关注 TornadoFX 指南,尝试 运行 示例向导: Wizard
我遇到了以下问题,请参阅第 3 项):
1) 当我按下 Cancel
时,值回滚到空值
下次开
2) 当我按下 Finish
时,这些值在下次打开时仍在向导中
3) 当我现在按下 Cancel
时,更改不会回滚到 2),而是回滚到
1), 一. e.空白字段
如何获得不同的 Cancel
行为:将 CustomerModel 回滚到上一个有效状态?
这是我的更新 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()
}
}
}
}
这是我的 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)
}
这是我的 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)
}
当您在 ItemViewModel
上调用 rollback()
时,它将回滚到在基础 item
中找到的数据,或者如果您从未支持 ItemViewModel
则返回空白值] 与一个项目。
在您的情况下,这意味着您需要将 Customer
分配给 CustomerModel
的 item
属性。完成后,您可以调用 rollback()
并且 CustomerModel
将显示它支持的 Customer
的状态。
如果您在重新打开向导时获得相同的状态,则意味着您重新打开了完全相同的向导实例。 Wizard 扩展了 View
,这使得它在其范围内成为单例,因此如果您只是调用 find()
来定位 Wizard 而未指定范围,则第二次尝试将获得与第一次相同的实例。
您没有 post 您的向导初始化代码,但如果您想避免这种情况,通常应该为向导创建一个新的范围。如果您希望向导编辑特定的客户实例,您应该这样做:
val model = CustomermerModel()
model.item = myCustomer
find<CustomerWizard>(Scope(model).openModal()
出于这个原因,让视图模型在构造函数中接受一个实例是正常的,您将其传递给 ItemViewModel
构造函数,以便自动为您分配项目:
class CustomerModel(customer: Customer? = null) : ItemViewModel<Customer>(customer)
并不是说我确保允许 CustomerModel 的无参数构造函数,以在范围内已经没有 CustomerModel 的情况下支持注入。在这种情况下,将创建一个新的 CustomerModel(不支持任何客户项目),因此这种情况需要一个无参数构造函数。
有了它,向导初始化代码就变成了:
find<CustomerWizard>(Scope(CustomerModel(myCustomer)).openModal()
希望这对您有所帮助。