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 分配给 CustomerModelitem 属性。完成后,您可以调用 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()

希望这对您有所帮助。