Kotlin 绑定不同视图模型的脏属性

Kotlin 绑定不同视图模型的脏属性,kotlin,tornadofx,Kotlin,Tornadofx,我有一个TornadFX应用程序,该应用程序遵循MVVM模式和模型: data class Person ( val name: String, val cars: List<Car> ) data class Car ( val brand: String, val model: String ) 应用程序定义了以下视图: 有一个列出所有人员的列表视图。除此之外,listView是一个详细信息视图,其中包含一个用于人名的文本字段和一个用于人名汽车

我有一个TornadFX应用程序,该应用程序遵循MVVM模式和模型:

data class Person (
    val name: String,
    val cars: List<Car>
)

data class Car (
    val brand: String,
    val model: String
)
应用程序定义了以下视图:

有一个列出所有人员的列表视图。除此之外,listView是一个详细信息视图,其中包含一个用于人名的文本字段和一个用于人名汽车的表视图。 双击表中的汽车条目会打开一个对话框,可以在其中编辑汽车的属性

我希望,如果我打开汽车详细信息并编辑条目,更改将反映在表视图中。由于我无法通过添加fx属性来改变汽车模型,这是一种不可变的类型,因此我提出了以下视图模型:

class PersonViewModel(): ItemViewModel<Person> {
    val name = bind(Person::name)
    val cars = bind { SimpleListProperty<CarViewModel>(item?.cars?.map{CarViewModel(it)}?.observable()) }

    override fun onCommit {
        // create new person based on ViewModel and store it
    }
}

class CarViewModel(item: Car): ItemViewModel<Car> {
    val brand = bind(Car::name)
    val model = bind(Car::model)

    init {
        this.item = item
    }
}
这样,如果双击表视图中的汽车条目并打开汽车详细信息视图,汽车的更新将直接反映在表视图中

我这里的问题是,我无法找到一种方法将表中所有CarViewModels的脏属性绑定到PersonViewModel。因此,如果我换了一辆车,PersonViewModel不会被标记为肮脏

有没有办法绑定PersonViewModel和CarViewModel的脏属性?如果选择了另一个人,还可以重新绑定它们


或者有更好的方法来定义我的视图模型吗?

我对框架进行了更改,允许视图模型绑定到列表,以观察列表更改事件。这使您能够通过以某种方式更改列表来触发列表属性的脏状态。仅仅更改列表中某个项目内的属性不会触发它,因此在下面的示例中,我只需在提交之前获取汽车的索引,并将汽车重新分配到相同的索引。这将触发一个ListChange事件,框架现在将侦听该事件

重要操作发生在Car对话框保存功能中:

button("Save").action {
    val index = person.cars.indexOf(car.item)

    car.commit {
        person.cars[index] = car.item
        close()
    }
}
在提交值之前记录car的索引,以确保equals/hashCode与相同的条目匹配,然后将新提交的项插入到相同的索引中,从而触发列表上的更改事件

这里是一个完整的示例,使用可变JavaFX属性,因为它们是惯用的JavaFX方式。您可以很容易地将其调整为使用不可变项,或者使用包装器

class Person(name: String, cars: List<Car>) {
    val nameProperty = SimpleStringProperty(name)
    var name by nameProperty

    val carsProperty = SimpleListProperty<Car>(FXCollections.observableArrayList(cars))
    var cars by carsProperty
}

class PersonModel : ItemViewModel<Person>() {
    val name = bind(Person::nameProperty)
    val cars: SimpleListProperty<Car> = bind(Person::carsProperty)
}


class Car(brand: String, model: String) {
    val brandProperty = SimpleStringProperty(brand)
    var brand by brandProperty

    val modelProperty = SimpleStringProperty(model)
    var model by modelProperty
}

class CarModel(car: Car? = null) : ItemViewModel<Car>(car) {
    val brand = bind(Car::brandProperty)
    val model = bind(Car::modelProperty)
}

class DataController : Controller() {
    val people = FXCollections.observableArrayList<Person>()

    init {
        people.add(
                Person("Person 1", listOf(Car("BMW", "M3"), Car("Ford", "Fiesta")))
        )
    }
}

class PersonMainView : View() {
    val data: DataController by inject()
    val selectedPerson: PersonModel by inject()

    override val root = borderpane {
        center {
            tableview(data.people) {
                column("Name", Person::nameProperty)
                bindSelected(selectedPerson)
            }
        }
        right(PersonEditor::class)
    }
}

class PersonEditor : View() {
    val person: PersonModel by inject()
    val selectedCar : CarModel by inject()

    override val root = form {
        fieldset {
            field("Name") {
                textfield(person.name).required()
            }
            field("Cars") {
                tableview(person.cars) {
                    column("Brand", Car::brandProperty)
                    column("Model", Car::modelProperty)
                    bindSelected(selectedCar)
                    onUserSelect(2) {
                        find<CarEditor>().openModal()
                    }
                }
            }
            button("Save") {
                enableWhen(person.dirty)
                action {
                    person.commit()
                }
            }
        }
    }
}

class CarEditor : View() {
    val car: CarModel by inject()
    val person: PersonModel by inject()

    override val root = form {
        fieldset {
            field("Brand") {
                textfield(car.brand).required()
            }
            field("Model") {
                textfield(car.model).required()
            }
            button("Save").action {
                val index = person.cars.indexOf(car.item)

                car.commit {
                    person.cars[index] = car.item
                    close()
                }
            }
        }
    }
}
该功能在TornadoFX 1.7.17-SNAPSHOT中提供