基本新手问题:
我想同步/绑定(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)
}
}
}
}
}
}
最佳答案
首先我们需要能够识别一个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/