TornadoFX JavaFX Sync Scroll across tableviews

风流意气都作罢 提交于 2019-12-25 07:49:11

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!