TornadFX JavaFX同步在表格视图之间滚动

TornadFX JavaFX同步在表格视图之间滚动,javafx,scrollbar,kotlin,tornadofx,Javafx,Scrollbar,Kotlin,Tornadofx,我正在尝试在表格视图中同步滚动条。(水平和垂直) SyncScrollEx视图有两个tableView,基本上是并排放置的一个片段,具有相同的数据集,因此具有相同的表大小布局 预期行为:当我在一个tableview上滚动时,另一个tableview的滚动条也应该滚动相同的数量 以下是我目前的进展: import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleStringPropert

我正在尝试在表格视图中同步滚动条。(水平和垂直)

SyncScrollEx视图有两个tableView,基本上是并排放置的一个片段,具有相同的数据集,因此具有相同的表大小布局

预期行为:当我在一个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()
导入javafx.beans.property.SimpleIntegerProperty
导入javafx.beans.property.SimpleStringProperty
导入javafx.collections.FXCollections
导入javafx.scene.control.ScrollBar
导入tornadofx*
类SyncScrollEx:View(){
覆盖val root=hbox{
setPrefSize(300.0、150.0)
this+=find()
this+=find()
}
}
类MyTableFrag:Fragment(){
var addEventOnlyOnceFlag=false
val persons=FXCollections.observearraylist(
GameWarrior(1,“提利昂·兰尼斯特”,“M”),
GameWarrior(2,“奈德·斯塔克”,“M”),
GameWarrior(3,“Sansa Stark”,“F”),
GameWarrior(4,“丹妮莉丝·坦格利安”,“F”),
GameWarrior(5,“布兰·斯塔克”,“M”),
GameWarrior(6,“乔恩·斯诺”,“M”),
GameWarrior(7,“雅利娅·斯塔克”,“F”)
)
覆盖val root=vbox{
tableview(人){
列(“ID”,GameWarrior::idProperty)
列(“名称”,GameWarrior::nameProperty)
列(“性别”,GameWarrior::genderProperty)
订阅{事件->
//同步两个表的ScrollX和ScrollY
event.node.value=event.newVal.toDouble()
}
//Hack,需要在呈现表格/滚动时初始化此项
SetonMouseCenter{
//没有在每次鼠标输入时触发lookupAll事件的攻击
如果(!addEventOnlyOnceFlag){
addEventOnlyOnceFlag=true
//信息:在tableView中查找滚动条并添加侦听器
this.lookupAll(“.scroll bar”).map{node->
if(节点是滚动条){
node.valueProperty().addListener{
值、旧值、新值->
println(node.orientation.toString()+“”+newValue)
火灾(SyncScrollEvent(节点,newValue))
}
}
}
}
}
}
}
}
类GameWarrior(id:Int,name:String,gender:String){
val idProperty=SimpleIntegerProperty(id)
按idProperty列出的变量id
val nameProperty=SimpleStringProperty(名称)
按名称属性的变量名称
val genderProperty=SimpleStringProperty(性别)
按性别属性划分的性别变量
}
类SyncScrollEvent(val节点:滚动条,val newVal:Number):FXEvent()
这些评论突出了我面临的问题。

此外,我无法理解在这种情况下,当Fire()发生在EventListener内部时,如何为这两个TableView调用“subscribe”(订阅)。首先,我们需要清楚地访问滚动条。当TableView被指定为其外观时,滚动条将可用。我们将创建一个以方向为关键点的地图,以跟踪它们:

val scrollbars = HashMap<Orientation, ScrollBar>()
我们不需要事件中的位置,因为我们可以查询滚动条的值,但是如果我们添加源TableView,则可以更容易地过滤掉事件。SyncScroll事件现在如下所示:

class SyncScrollEvent(val scrollbar: ScrollBar, val table: TableView<*>) : FXEvent()
类SyncScrollEvent(val scrollbar:scrollbar,val table:TableView):FXEvent()
让我们听听滚动事件,并确保只有当事件源自另一个tableview时,我们才更改滚动条值,以获得相应的方向:

subscribe<SyncScrollEvent> { event ->
    if (event.table != this)
        scrollbars[event.scrollbar.orientation]?.value = event.scrollbar.value
}
订阅{事件->
if(event.table!=此)
滚动条[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()
导入javafx.beans.property.SimpleIntegerProperty
导入javafx.beans.property.SimpleStringProperty
导入javafx.collections.FXCollections
导入javafx.geometry.Orientation
导入javafx.scene.control.ScrollBar
导入javafx.scene.control.TableView
导入tornadofx*
导入java.util*
类SyncScrollEx:View(){
覆盖val root=hbox{
setPrefSize(300.0、150.0)
添加(MyTableFrag::类)
添加(MyTableFrag::类)
}
}
类MyTableFrag:Fragment(){
val persons=FXCollections.observearraylist(
GameWarrior(1,“提利昂·兰尼斯特”,“M”),
GameWarrior(2,“奈德·斯塔克”,“M”),
GameWarrior(3,“Sansa Stark”,“F”),
GameWarrior(4,“丹妮莉丝·坦格利安”,“F”),
GameWarrior(5,“布兰·斯塔克”,“M”),
GameWarrior(6,“乔恩·斯诺”,“M”),
GameWarrior(7,“雅利娅·斯塔克”,“F”)
)
val scrollbars=HashMap()
覆盖val root=vbox{
tableview(人){
列(“ID”,GameWarrior::idProperty)
列(“名称”,GameWarrior::nameProperty)
列(“性别”,GameWarrior::genderProperty)
订阅{事件->
if(event.table!=此)
滚动条[event.scrollbar.orientation]?.value=event.scrollbar.value
}
skinProperty().onChange{
this.lookupAll(“.ScrollBar”).map{it as ScrollBar}.forEach{bar->
滚动条[条方向]=条
bar.valueProperty().onChange{
火灾(同步滚动事件(条形图,本))
}
}
}
}
}
}
类GameWarrior(id:Int,name:String,gender:String){
val idProperty=SimpleIntegerProperty(id)
按idProperty列出的变量id
val nameProperty=SimpleStringProperty(名称)
按名称属性的变量名称
val genderProperty=SimpleStringProperty(性别)
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()