javafx - Tornadofx tableview 同步两个表

标签 javafx kotlin tornadofx

基本新手问题:

我想同步/绑定(bind)两个表。
为了使示例简单,我使用了两个单独的 TableView 。这需要使用片段和范围来完成,我认为这会使问题复杂化,因为我遇到了一个基本问题。
行为:单击表 1 的同步按钮时,我希望表 1 选择的数据覆盖相应的表 2 数据。反之亦然

人物模型:

class Person(firstName: String = "", lastName: String = "") {
    val firstNameProperty = SimpleStringProperty(firstName)
    var firstName by firstNameProperty
    val lastNameProperty = SimpleStringProperty(lastName)
    var lastName by lastNameProperty
}

class PersonModel : ItemViewModel<Person>() {
    val firstName = bind { item?.firstNameProperty }
    val lastName = bind { item?.lastNameProperty }
}

个人 Controller (虚拟数据):

class PersonController : Controller(){
    val persons = FXCollections.observableArrayList<Person>()
    val newPersons = FXCollections.observableArrayList<Person>()
    init {
        persons += Person("Dead", "Stark")
        persons += Person("Tyrion", "Lannister")
        persons += Person("Arya", "Stark")
        persons += Person("Daenerys", "Targaryen")

        newPersons += Person("Ned", "Stark")
        newPersons += Person("Tyrion", "Janitor")
        newPersons += Person("Arya", "Stark")
        newPersons += Person("Taenerys", "Dargaryen")
    }
}

人员 ListView :

class PersonList : View() {
    val ctrl: PersonController by inject()
    val model : PersonModel by inject()
    var personTable : TableView<Person> by singleAssign()
    override val root = VBox()
    init {
        with(root) {
            tableview(ctrl.persons) {
                personTable = this
                column("First Name", Person::firstNameProperty)
                column("Last Name", Person::lastNameProperty)
                columnResizePolicy = SmartResize.POLICY
            }
            hbox {
                button("Sync") {
                    setOnAction {
                        personTable.bindSelected(model)
                        //model.itemProperty.bind(personTable.selectionModel.selectedItemProperty())
                    }
                }
            }
        }
    }

另一个人 ListView :

class AnotherPersonList : View() {
    val model : PersonModel by inject()
    val ctrl: PersonController by inject()
    override val root = VBox()
    var newPersonTable : TableView<Person> by singleAssign()
    init {
        with(root) {
            tableview(ctrl.newPersons) {
                newPersonTable = this
                column("First Name", Person::firstNameProperty)
                column("Last Name", Person::lastNameProperty)
                columnResizePolicy = SmartResize.POLICY
            }
            hbox {
                button("Sync") {
                    setOnAction {
                        newPersonTable.bindSelected(model)
                    }
                }
            }
        }
    }
}

Sync Two Tables

最佳答案

首先我们需要能够识别一个Person,所以在Person对象中包含equals/hashCode:

class Person(firstName: String = "", lastName: String = "") {
    val firstNameProperty = SimpleStringProperty(firstName)
    var firstName by firstNameProperty
    val lastNameProperty = SimpleStringProperty(lastName)
    var lastName by lastNameProperty

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other?.javaClass != javaClass) return false

        other as Person

        if (firstName != other.firstName) return false
        if (lastName != other.lastName) return false

        return true
    }

    override fun hashCode(): Int {
        var result = firstName.hashCode()
        result = 31 * result + lastName.hashCode()
        return result
    }

}

我们希望在您单击“同步”按钮时触发一个事件,因此我们定义了一个可以同时包含所选人员和行索引的事件:

class SyncPersonEvent(val person: Person, val index: Int) : FXEvent()

您不能在两个 View 中注入(inject)相同的 PersonModel 实例并使用 bindSelected,因为那样会相互覆盖。此外,bindSelected 会在选择更改时使用react,而不是在您调用 bindSelected 本身时使用react,因此它不属于按钮处理程序。我们将为每个 View 使用一个单独的模型并绑定(bind)到选择。然后我们可以很容易地知道当按钮处理程序运行时选择了什么人,我们不需要持有 TableView 的实例。我们还将使用新的根生成器语法来清理所有内容。这是 PersonList View :

class PersonList : View() {
    val ctrl: PersonController by inject()
    val selectedPerson = PersonModel()

    override val root = vbox {
        tableview(ctrl.persons) {
            column("First Name", Person::firstNameProperty)
            column("Last Name", Person::lastNameProperty)
            columnResizePolicy = SmartResize.POLICY
            bindSelected(selectedPerson)
            subscribe<SyncPersonEvent> { event ->
                if (!items.contains(event.person)) {
                    items.add(event.index, event.person)
                }
                if (selectedItem != event.person) {
                    requestFocus()
                    selectionModel.select(event.person)
                }
            }
        }
        hbox {
            button("Sync") {
                setOnAction {
                    selectedPerson.item?.apply {
                        fire(SyncPersonEvent(this, ctrl.persons.indexOf(this)))
                    }
                }

            }
        }
    }
}

除了在两个地方引用 ctrl.newPersons 而不是 ctrl.persons 之外,AnotherPersonList View 是相同的。 (您可以使用相同的片段并将列表作为参数发送,这样您就不需要复制所有这些代码)。

同步按钮现在触发我们的事件,前提是在单击按钮时选择了一个人:

selectedPerson.item?.apply {
    fire(SyncPersonEvent(this, ctrl.persons.indexOf(this)))
}

在 TableView 中,我们现在订阅 SyncPersonEvent:

subscribe<SyncPersonEvent> { event ->
    if (!items.contains(event.person)) {
        items.add(event.index, event.person)
    }
    if (selectedItem != event.person) {
        requestFocus()
        selectionModel.select(event.person)
    }
}

同步事件在事件触发时得到通知。它首先检查 TableView 的项目是否包含此人,如果不包含,则将其添加到正确的索引处。真正的应用程序应该检查索引是否在项目列表的范围内。

然后它检查这个人是否已经被选中,如果没有,它会进行选择并请求焦点到这个表。检查很重要,这样源表就不会请求焦点或执行(冗余)选择。

如前所述,一个好的优化方法是将项目列表作为参数发送,这样您就不需要复制 PersonList 代码。

另请注意新构建器语法的使用:

override val root = vbox {
}

这比首先将根节点声明为 VBox() 并在 init block 中构建 UI 的其余部分要简洁得多。

希望这就是您要找的:)

重要提示:此解决方案需要 TornadoFX 1.5.9。它将于今天发布 :) 如果您愿意,您可以同时针对 1.5.9-SNAPSHOT 进行构建。

关于javafx - Tornadofx tableview 同步两个表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41299769/

相关文章:

java - IntelliJ 不会编译从文档复制的基本 JavaFX 程序

java - java中可以返回矩形对象的颜色吗?

Kotlin 测试 float 是否处于开区间或半开区间

javafx - 如何导出 tornadofx 应用程序?

java - 如何使用 gradle 任务运行 Shadow jar?

kotlin - 有关Kotlin的一些基本问题

JavaFX 和 Swing 性能问题

java - 是否可以在 openJRE 没有 java FX 的环境中运行 JavaFX jar?

javafx - 如何在 TornadoFX 中将 minHeight 和 minWidth 设置为窗口?