edvin / tornadofx

Lightweight JavaFX Framework for Kotlin
Apache License 2.0
3.67k stars 269 forks source link

Accessing nodes with less code #1344

Closed croissant676 closed 2 years ago

croissant676 commented 2 years ago

This issue isn't an actual issue, it's just a question I have about tornadofx. When coding something like

        hbox {
            vbox {
                textfield("Things")
            }
            button ("Print value") {
                action {
                    println("Something") // Wants to print textfield text
                }
            }
        }

Is there an easy way to access the textfield? (Or nodes in general?) Typically, I do something like this:

        hbox {
            var textfield: TextField by Delegates.notNull()
            vbox {
                textfield = textfield("Some more things")
            }
            button ("Print value") {
                action {
                    println(textfield.text)
                }
            }
        }

This feels more complex than it needs to be. Is there a solution like this

        hbox {
            vbox {
                textfield("Even more things") {
                    link = 0
                }
            }
            button ("Print value") {
                action {
                    println((nodes[0] as TextField).text)
                    println(nodes<TextField>(0).text) // Or this
                }
            }
        }

inside of tornadofx already? And is there already an easier way to access nodes that I don't know about? (Which is very likely, since I'm not that experienced with kotlin or tornadofx)

This is for all nodes in general.

Thanks in advance!

edvin commented 2 years ago

You can if you absolutely need to assign the ui elements to a var like you do above, but almost every time I see someone do this, what they really want access to is the data. If you just use data binding you have no need to access the UI element directly, you'd just listen to changes to your model or access the model property directly. You can also fire events to get around the tight coupling to ui elements, which is an anti pattern you should avoid. Perhaps if you can show what you're trying to do, we can show you the correct solution.

croissant676 commented 2 years ago

My code looks something like this:


    var daysSpinner: Spinner<Int> by Delegates.notNull()
    // UI Section
    borderpane {
        center {
            vbox {
                hbox {
                    // Other UI Elements
                    daysSpinner = spinner(1, 100, 5, 1, false)
                }
              // Other UI Elements
             }
        }
        bottom {
            hbox {
                // Other UI Elements
                button("Next > ") {
                    action {
                        days = daysSpinner.value
                        nextView()
                    }
                }
                paddingBottom = 30.0
                alignment = Pos.CENTER
                spacing = 30.0
            }
        }
    }
edvin commented 2 years ago

This is exactly the situation I was talking about :) Here is a version of your code which binds the spinner to a Property and accesses the property value in the action, without having a tight coupling to the UI element:

val dayProperty = SimpleIntegerProperty(42)

override val root = borderpane {
    center {
        vbox {
            hbox {
                spinner(1, 100, 5, 1, false, dayProperty)
            }
        }
    }
    bottom {
        hbox {
            button("Next > ") {
                action {
                    println("You have access to ${dayProperty.value} here")
                }
            }
        }
    }
}
croissant676 commented 2 years ago

It worked for me :) Thanks!