问题
I am trying to synchronise scrolls across the tableviews. (Both Horizontal & Vertical)
The SyncScrollEx View has two tableView which is basically one Fragment placed side by side, with same dataset, and hence same table size layout.
Expected Behaviour: When I scroll on one tableview, the other tableview's scrollbar should also scroll for the same amount.
Below is my current progress:
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import javafx.scene.control.ScrollBar
import tornadofx.*
class SyncScrollEx : View() {
override val root = hbox {
setPrefSize(300.0, 150.0)
this += find<MyTableFrag>()
this += find<MyTableFrag>()
}
}
class MyTableFrag : Fragment() {
var addEventOnlyOnceFlag = false
val persons = FXCollections.observableArrayList<GameWarrior>(
GameWarrior(1,"Tyrion Lannister", "M"),
GameWarrior(2,"Ned Stark", "M"),
GameWarrior(3,"Sansa Stark", "F"),
GameWarrior(4,"Daenerys Targaryen", "F"),
GameWarrior(5,"Bran Stark", "M"),
GameWarrior(6,"Jon Snow", "M"),
GameWarrior(7,"Arya Stark", "F")
)
override val root = vbox {
tableview(persons) {
column("ID", GameWarrior::idProperty)
column("Name", GameWarrior::nameProperty)
column("Gender", GameWarrior::genderProperty)
subscribe<SyncScrollEvent> { event ->
//Sync the ScrollX & ScrollY of both the tables
event.node.value = event.newVal.toDouble()
}
//Hack, need to initialize this when the table/scroll is rendered
setOnMouseEntered {
//Hack for not triggering the lookupAll event on every mouse enter
if (!addEventOnlyOnceFlag) {
addEventOnlyOnceFlag = true
//INFO: Look up for the scroll bars in tableView and add a listener
this.lookupAll(".scroll-bar").map { node ->
if (node is ScrollBar) {
node.valueProperty().addListener {
value, oldValue, newValue ->
println(node.orientation.toString() + " " + newValue)
fire(SyncScrollEvent(node, newValue))
}
}
}
}
}
}
}
}
class GameWarrior(id: Int, name: String, gender: String) {
val idProperty = SimpleIntegerProperty(id)
var id by idProperty
val nameProperty = SimpleStringProperty(name)
var name by nameProperty
val genderProperty = SimpleStringProperty(gender)
var gender by genderProperty
}
class SyncScrollEvent(val node: ScrollBar, val newVal: Number) : FXEvent()
The comments highlight the problems I am facing.
Also, I fail to understand how the "subscribe" will get invoked for both the tableviews in such scenario where Fire() happens inside a EventListener
回答1:
First we need clean access to the scrollbars. When the TableView is assigned it's skin, the scrollbars will be available. We'll create a map keyed on orientation to keep track of them:
val scrollbars = HashMap<Orientation, ScrollBar>()
Once the skin is available we look up the scrollbars and assign them to our map and listen for changes so we can fire the event
skinProperty().onChange {
this.lookupAll(".scroll-bar").map { it as ScrollBar }.forEach { bar ->
scrollbars[bar.orientation] = bar
bar.valueProperty().onChange {
fire(SyncScrollEvent(bar, this))
}
}
}
We don't need the position in the event, since we can query the scrollbar for it's value, but it's easier to filter out the events if we add the source TableView. The SyncScrollEvent now looks like this:
class SyncScrollEvent(val scrollbar: ScrollBar, val table: TableView<*>) : FXEvent()
Let's listen for the scroll events and make sure we only change our scrollbar value if the event originates from the other tableview, for the corresponding orientation:
subscribe<SyncScrollEvent> { event ->
if (event.table != this)
scrollbars[event.scrollbar.orientation]?.value = event.scrollbar.value
}
For completeness, here is the whole modified app:
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import javafx.geometry.Orientation
import javafx.scene.control.ScrollBar
import javafx.scene.control.TableView
import tornadofx.*
import java.util.*
class SyncScrollEx : View() {
override val root = hbox {
setPrefSize(300.0, 150.0)
add(MyTableFrag::class)
add(MyTableFrag::class)
}
}
class MyTableFrag : Fragment() {
val persons = FXCollections.observableArrayList<GameWarrior>(
GameWarrior(1, "Tyrion Lannister", "M"),
GameWarrior(2, "Ned Stark", "M"),
GameWarrior(3, "Sansa Stark", "F"),
GameWarrior(4, "Daenerys Targaryen", "F"),
GameWarrior(5, "Bran Stark", "M"),
GameWarrior(6, "Jon Snow", "M"),
GameWarrior(7, "Arya Stark", "F")
)
val scrollbars = HashMap<Orientation, ScrollBar>()
override val root = vbox {
tableview(persons) {
column("ID", GameWarrior::idProperty)
column("Name", GameWarrior::nameProperty)
column("Gender", GameWarrior::genderProperty)
subscribe<SyncScrollEvent> { event ->
if (event.table != this)
scrollbars[event.scrollbar.orientation]?.value = event.scrollbar.value
}
skinProperty().onChange {
this.lookupAll(".scroll-bar").map { it as ScrollBar }.forEach { bar ->
scrollbars[bar.orientation] = bar
bar.valueProperty().onChange {
fire(SyncScrollEvent(bar, this))
}
}
}
}
}
}
class GameWarrior(id: Int, name: String, gender: String) {
val idProperty = SimpleIntegerProperty(id)
var id by idProperty
val nameProperty = SimpleStringProperty(name)
var name by nameProperty
val genderProperty = SimpleStringProperty(gender)
var gender by genderProperty
}
class SyncScrollEvent(val scrollbar: ScrollBar, val table: TableView<*>) : FXEvent()
来源:https://stackoverflow.com/questions/41462473/tornadofx-javafx-sync-scroll-across-tableviews