Tornadofx:如何减少 table+数据编辑器 类 中的冗余?
Tornadofx: How can I reduce redundancy in table+editor of data classes?
我对 Kotlon、JavaFX 和 Tornadofx 还很陌生。我喜欢我目前所看到的 :)
(也是 Whosebug 的新手,我希望我没有把它拉得太远......
我有一个应用程序可以检索远程 JSON 数据。该数据被编辑并作为 JSON.
返回到服务器
editable table 视图显示此数据(包含嵌套属性),旁边的编辑器也用于编辑。
我附上了一个示例应用程序来演示这一点。这对我来说似乎很多余而且太复杂了。我想这里一定有问题:)
感谢您提供的任何指导/帮助! :)
- 代码对我来说似乎过于多余。理想情况下,我想坚持使用数据 类 但无法使用 POJO 使其成为 editable 。
- 此外,在 table 中使用嵌套地址属性的方式对我来说似乎有点不对劲。其他数据 类 也会有地址,每次都复制属性肯定不好。
(模型没有将更改写回 POJO。我已经使用 SimpleStringProperty 的补丁版本修复了这个问题,但我什至不确定我使用它的方式是否是最好的方法。)
package com.jadev.office
import javafx.application.Application
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import javafx.geometry.Orientation
import tornadofx.*
// data classes obtained by parsing JSON from remote server, will be send back to the server to update the data
// Address should be used in several other classes
data class Address(var street: String, var city: String, var country: String)
data class Person(var name: String, var address: Address)
// Editable versions to use in table and editor view, this is wrapping the original data
// to avoid duplicating the properties
class EditableAddress(var address: Address) {
var streetProperty = SimpleStringProperty(address, "street", address.street)
var cityProperty = SimpleStringProperty(address, "city", address.city)
var countryProperty = SimpleStringProperty(address, "country", address.country)
}
class EditablePerson(val person: Person) {
val nameProperty = SimpleStringProperty(person, "name", person.name)
val editableAddress = EditableAddress(person.address)
val addressProperty = SimpleObjectProperty(editableAddress)
val streetProperty = addressProperty.select(EditableAddress::streetProperty)
val cityProperty = addressProperty.select(EditableAddress::cityProperty)
val countryProperty = addressProperty.select(EditableAddress::countryProperty)
}
class PersonViewModel : ItemViewModel<EditablePerson>() {
var name = bind(EditablePerson::nameProperty)
var street = bind(EditablePerson::streetProperty)
var city = bind(EditablePerson::cityProperty)
var country = bind(EditablePerson::countryProperty)
}
val persons = mutableListOf(
Person("Adam", Address("Paradise 1", "Eden", "ED")),
Person("Eve", Address("Paradise 1", "Eden", "ED")))
class MainView : View() {
val editablePersons = FXCollections.observableArrayList<EditablePerson>(persons.map { EditablePerson(it) })
val model: PersonViewModel by inject()
override val root = splitpane(Orientation.HORIZONTAL) {
tableview(editablePersons) {
column("Name", EditablePerson::nameProperty)
column("Street", EditablePerson::streetProperty)
column("City", EditablePerson::cityProperty)
column("Country", EditablePerson::countryProperty)
bindSelected(model)
}
form {
fieldset {
label("Name:")
textfield(model.name)
label("Street:")
textfield(model.street)
label("City:")
textfield(model.city)
label("Country:")
textfield(model.country)
}
button("Save") {
action {
save()
}
}
}
}
fun save() {
//for some reason this is updating the table but not the data wrapped by SimpleStringProperty()
model.commit()
println("Updated person: ${model.item.person}")
println("Updated persons: ${persons}")
}
}
class SampleApp : App(MainView::class)
fun main(args: Array<String>) {
Application.launch(SampleApp::class.java, *args)
}
我确保始终只有一个域对象和该对象对应的 ViewModel。我也喜欢直接在那里实现 JsonModel
和处理 serialization/deserialization。我从未见过优化的必要性,因此我不会在不需要的地方使用可观察值,所以我只是坚持这种模式。
确保每个对象都有一个 noargs 构造函数也是一个好主意,这样它们就可以在不调用专门构造函数的情况下被实例化。
您应该避免在对象的不同版本之间进行转换,如您所见,您只会 运行 陷入麻烦和细微的错误中。相反,我直接在 TableView 中对地址进行对象遍历。不是最漂亮的代码,但是更透明
不过,我确实喜欢您在人员视图模型中公开地址字段的想法,以便它们可以与人员一起提交。我已经更改了您的应用程序以反映这些想法。
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.geometry.Orientation
import tornadofx.*
class Person() {
val nameProperty = SimpleStringProperty()
var name by nameProperty
val addressProperty = SimpleObjectProperty<Address>()
var address by addressProperty
constructor(name: String, address: Address): this() {
this.name = name
this.address = address
}
override fun toString() = "$name - $address"
}
class Address() {
val streetProperty = SimpleStringProperty()
var street by streetProperty
val cityProperty = SimpleStringProperty()
var city by cityProperty
val countryProperty = SimpleStringProperty()
var country by countryProperty
constructor(street: String, city: String, country: String) : this() {
this.street = street
this.city = city
this.country = country
}
override fun toString() = "$street, $city, $country"
}
class PersonModel : ItemViewModel<Person>() {
val name = bind(Person::nameProperty)
val address = bind(Person::addressProperty)
val street = address.select(Address::streetProperty)
val city = address.select(Address::cityProperty)
val country = address.select(Address::countryProperty)
}
// The data would probably come from a controller in a real app
val persons = mutableListOf(
Person("Adam", Address("Paradise 1", "Eden", "ED")),
Person("Eve", Address("Paradise 1", "Eden", "ED"))).observable()
class MainView : View() {
val model: PersonModel by inject()
override val root = splitpane(Orientation.HORIZONTAL) {
tableview(persons) {
column("Name", Person::nameProperty)
column<Person, String>("Street", { it.value.addressProperty.value.streetProperty })
column<Person, String>("City", { it.value.addressProperty.value.cityProperty })
column<Person, String>("Country", { it.value.addressProperty.value.countryProperty })
bindSelected(model)
}
form {
fieldset {
label("Name:")
textfield(model.name)
label("Street:")
textfield(model.street)
label("City:")
textfield(model.city)
label("Country:")
textfield(model.country)
}
button("Save") {
action {
save()
}
}
}
}
fun save() {
model.commit()
println("Updated person: ${model.item}")
println("Updated persons: $persons")
}
}
class SampleApp : App(MainView::class)
fun main(args: Array<String>) {
launch<SampleApp>(args)
}
我对 Kotlon、JavaFX 和 Tornadofx 还很陌生。我喜欢我目前所看到的 :) (也是 Whosebug 的新手,我希望我没有把它拉得太远......
我有一个应用程序可以检索远程 JSON 数据。该数据被编辑并作为 JSON.
返回到服务器editable table 视图显示此数据(包含嵌套属性),旁边的编辑器也用于编辑。
我附上了一个示例应用程序来演示这一点。这对我来说似乎很多余而且太复杂了。我想这里一定有问题:)
感谢您提供的任何指导/帮助! :)
- 代码对我来说似乎过于多余。理想情况下,我想坚持使用数据 类 但无法使用 POJO 使其成为 editable 。
- 此外,在 table 中使用嵌套地址属性的方式对我来说似乎有点不对劲。其他数据 类 也会有地址,每次都复制属性肯定不好。
(模型没有将更改写回 POJO。我已经使用 SimpleStringProperty 的补丁版本修复了这个问题,但我什至不确定我使用它的方式是否是最好的方法。)
package com.jadev.office import javafx.application.Application import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleStringProperty import javafx.collections.FXCollections import javafx.geometry.Orientation import tornadofx.* // data classes obtained by parsing JSON from remote server, will be send back to the server to update the data // Address should be used in several other classes data class Address(var street: String, var city: String, var country: String) data class Person(var name: String, var address: Address) // Editable versions to use in table and editor view, this is wrapping the original data // to avoid duplicating the properties class EditableAddress(var address: Address) { var streetProperty = SimpleStringProperty(address, "street", address.street) var cityProperty = SimpleStringProperty(address, "city", address.city) var countryProperty = SimpleStringProperty(address, "country", address.country) } class EditablePerson(val person: Person) { val nameProperty = SimpleStringProperty(person, "name", person.name) val editableAddress = EditableAddress(person.address) val addressProperty = SimpleObjectProperty(editableAddress) val streetProperty = addressProperty.select(EditableAddress::streetProperty) val cityProperty = addressProperty.select(EditableAddress::cityProperty) val countryProperty = addressProperty.select(EditableAddress::countryProperty) } class PersonViewModel : ItemViewModel<EditablePerson>() { var name = bind(EditablePerson::nameProperty) var street = bind(EditablePerson::streetProperty) var city = bind(EditablePerson::cityProperty) var country = bind(EditablePerson::countryProperty) } val persons = mutableListOf( Person("Adam", Address("Paradise 1", "Eden", "ED")), Person("Eve", Address("Paradise 1", "Eden", "ED"))) class MainView : View() { val editablePersons = FXCollections.observableArrayList<EditablePerson>(persons.map { EditablePerson(it) }) val model: PersonViewModel by inject() override val root = splitpane(Orientation.HORIZONTAL) { tableview(editablePersons) { column("Name", EditablePerson::nameProperty) column("Street", EditablePerson::streetProperty) column("City", EditablePerson::cityProperty) column("Country", EditablePerson::countryProperty) bindSelected(model) } form { fieldset { label("Name:") textfield(model.name) label("Street:") textfield(model.street) label("City:") textfield(model.city) label("Country:") textfield(model.country) } button("Save") { action { save() } } } } fun save() { //for some reason this is updating the table but not the data wrapped by SimpleStringProperty() model.commit() println("Updated person: ${model.item.person}") println("Updated persons: ${persons}") } } class SampleApp : App(MainView::class) fun main(args: Array<String>) { Application.launch(SampleApp::class.java, *args) }
我确保始终只有一个域对象和该对象对应的 ViewModel。我也喜欢直接在那里实现 JsonModel
和处理 serialization/deserialization。我从未见过优化的必要性,因此我不会在不需要的地方使用可观察值,所以我只是坚持这种模式。
确保每个对象都有一个 noargs 构造函数也是一个好主意,这样它们就可以在不调用专门构造函数的情况下被实例化。
您应该避免在对象的不同版本之间进行转换,如您所见,您只会 运行 陷入麻烦和细微的错误中。相反,我直接在 TableView 中对地址进行对象遍历。不是最漂亮的代码,但是更透明
不过,我确实喜欢您在人员视图模型中公开地址字段的想法,以便它们可以与人员一起提交。我已经更改了您的应用程序以反映这些想法。
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.geometry.Orientation
import tornadofx.*
class Person() {
val nameProperty = SimpleStringProperty()
var name by nameProperty
val addressProperty = SimpleObjectProperty<Address>()
var address by addressProperty
constructor(name: String, address: Address): this() {
this.name = name
this.address = address
}
override fun toString() = "$name - $address"
}
class Address() {
val streetProperty = SimpleStringProperty()
var street by streetProperty
val cityProperty = SimpleStringProperty()
var city by cityProperty
val countryProperty = SimpleStringProperty()
var country by countryProperty
constructor(street: String, city: String, country: String) : this() {
this.street = street
this.city = city
this.country = country
}
override fun toString() = "$street, $city, $country"
}
class PersonModel : ItemViewModel<Person>() {
val name = bind(Person::nameProperty)
val address = bind(Person::addressProperty)
val street = address.select(Address::streetProperty)
val city = address.select(Address::cityProperty)
val country = address.select(Address::countryProperty)
}
// The data would probably come from a controller in a real app
val persons = mutableListOf(
Person("Adam", Address("Paradise 1", "Eden", "ED")),
Person("Eve", Address("Paradise 1", "Eden", "ED"))).observable()
class MainView : View() {
val model: PersonModel by inject()
override val root = splitpane(Orientation.HORIZONTAL) {
tableview(persons) {
column("Name", Person::nameProperty)
column<Person, String>("Street", { it.value.addressProperty.value.streetProperty })
column<Person, String>("City", { it.value.addressProperty.value.cityProperty })
column<Person, String>("Country", { it.value.addressProperty.value.countryProperty })
bindSelected(model)
}
form {
fieldset {
label("Name:")
textfield(model.name)
label("Street:")
textfield(model.street)
label("City:")
textfield(model.city)
label("Country:")
textfield(model.country)
}
button("Save") {
action {
save()
}
}
}
}
fun save() {
model.commit()
println("Updated person: ${model.item}")
println("Updated persons: $persons")
}
}
class SampleApp : App(MainView::class)
fun main(args: Array<String>) {
launch<SampleApp>(args)
}