TornadoFX JavaFX Sync滚动浏览表格

时间:2017-01-04 11:14:39

标签: javafx scrollbar kotlin tornadofx

我正在尝试在tableviews上同步滚动。 (水平和垂直)

enter image description here

SyncScrollEx View有两个tableView,它基本上是一个并排放置的Fragment,具有相同的数据集,因此具有相同的表大小布局。

预期行为:当我滚动一个tableview时,另一个tableview的滚动条也应该滚动相同的数量。

以下是我目前的进展:

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()

评论突出了我所面临的问题 另外,我无法理解如何在EventListener中发生Fire()的场景中为两个tableviews调用“subscribe”

1 个答案:

答案 0 :(得分:3)

首先,我们需要对滚动条进行干净访问。当为TableView分配它的皮肤时,滚动条将可用。我们将创建一个键入方向的地图以跟踪它们:

val scrollbars = HashMap<Orientation, ScrollBar>()

一旦皮肤可用,我们会查找滚动条并将它们分配到我们的地图并听取更改,以便我们可以触发事件

skinProperty().onChange {
    this.lookupAll(".scroll-bar").map { it as ScrollBar }.forEach { bar ->
        scrollbars[bar.orientation] = bar
        bar.valueProperty().onChange {
            fire(SyncScrollEvent(bar, this))
        }
    }
}

我们不需要事件中的位置,因为我们可以查询滚动条的值,但如果我们添加源TableView,则更容易过滤掉事件。 SyncScrollEvent现在看起来像这样:

class SyncScrollEvent(val scrollbar: ScrollBar, val table: TableView<*>) : FXEvent()

让我们听一下滚动事件,并确保我们只在事件来自其他桌面视图时更改我们的滚动条值,以获得相应的方向:

subscribe<SyncScrollEvent> { event ->
    if (event.table != this)
        scrollbars[event.scrollbar.orientation]?.value = event.scrollbar.value
}

为了完整性,这里是整个修改过的应用程序:

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()