Basic newbie question:
I want to sync/bind two tables.
For keeping the example simple, I have used two separate table views. This needs to be done using fragments and scope, which I thought would complicate the question as I am stuck at a basic problem.
Behaviour: On clicking the sync button of table 1 , I want table 1 selected data to override the corresponding table 2 data. and vice-versa
Person Model:
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 }
}
Person Controller (dummy data):
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")
}
}
Person List View:
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())
}
}
}
}
}
Another Person List View:
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)
}
}
}
}
}
}
First we need to be able to identify a Person, so include equals/hashCode in the Person object:
We want to fire an event when you click the Sync button, so we define an event that can contain both the selected person and the row index:
You cannot inject the same PersonModel instance and use
bindSelected
in both views, since that will override each other. Also,bindSelected
will react whenever the selection changes, not when you callbindSelected
itself, so it doesn't belong in the button handler. We'll use a separate model for each view and bind towards the selection. Then we can easily know what person is selected when the button handler runs, and we don't need to hold on to an instance of the TableView. We'll also use the new root builder syntax to clean up everything. Here is the PersonList view:The
AnotherPersonList
view is identical except for the reference toctrl.newPersons
instead ofctrl.persons
in two places. (You might use the same fragment and send in the list as a parameter so you don't need to duplicate all this code).The sync button now fires our event, provided that a person is selected at the time of the button click:
Inside the TableView we now subscribe to the
SyncPersonEvent
:The sync event is notified when the event fires. It first checks if the items of for the tableview contains this person, or adds it at the correct index if not. A real application should check that the index is within the bounds of the items list.
Then it checks if this person is selected already and if not it will make the selection and also request focus to this table. The check is important so that the source table doesn't also request focus or perform the (redundant) selection.
As noted, a good optimization would be to send in the items list as a parameter so that you don't need to duplicate the PersonList code.
Also notice the use of the new builder syntax:
This is much neater than first declaring the root node as a
VBox()
and when building the rest of the UI in theinit
block.Hope this is what you're looking for :)
Important: This solution requires TornadoFX 1.5.9. It will be released today :) You can build against 1.5.9-SNAPSHOT in the mean time if you like.