How to dynamically add Nodes to VBox in ScalaFX?

152 views
Skip to first unread message

gotw42

unread,
May 28, 2020, 9:53:38 AM5/28/20
to ScalaFX Users

My ScalaFX app has a Button that adds a GridPane to a VBox. I tried directly updating the VBox children in Button.onAction but that causes the Button to become non-responsive after rendering the first added GridPane. I am now trying the following code:

import scalafx.scene.{Node, Scene}
import scalafx.application.JFXApp
import scalafx.geometry.Insets
import scalafx.scene.control.{Button, Label}
import scalafx.scene.layout.{GridPane, VBox}
import scalafx.Includes._
import scalafx.collections.ObservableBuffer

object Gui extends JFXApp {

  stage = new JFXApp.PrimaryStage {
    title = "DSP Lab"
    scene = new Scene(320, 300) {

      val bufferOfNodes = new ObservableBuffer[Node]

      val vBox = new VBox {
        children = bufferOfNodes // assign ObservableBuffer to children
      }

      val addGridPaneButton = new Button {
        text = "Add GridPane to VBox"
        onAction = _ => {
          bufferOfNodes += getNewGridPane // update the ObservableBuffer
        }
      }

      content = List(addGridPaneButton, vBox)
    }
  }

  stage.setX(100)
  stage.setY(100)

  def getNewGridPane: GridPane = new GridPane {
    vgap = 5
    hgap = 10
    padding = Insets(20)

    val sampleFrequencyLabel = new Label("Sample Frequency:")
    val sampleFrequencyField = new DoubleField
    val amplitudeLabel = new Label("Amplitude:")
    val amplitudeField = new DoubleField
    add(sampleFrequencyLabel, 0, 0)
    add(sampleFrequencyField, 1, 0)
    add(amplitudeLabel, 0, 1)
    add(amplitudeField, 1, 1)
  }
}

By assigning VBox.children to the ObservableBuffer[Node] the Button stays responsive and onAction adds the GridPane to bufferOfNodes but the GridPanes seem to never get added to the VBox and they are never rendered.

Is there a way to do this?

Jarek Sacha

unread,
May 28, 2020, 9:47:40 PM5/28/20
to scalaf...@googlegroups.com
 You have two issues. `bufferOfNodes` is not replacing `children` as you think, so content is not updated. When you assign directly to Scene's `content` you pileup nodes on top of each other, so the button is not responding after first addition. See also comments below.

On Thu, May 28, 2020 at 9:53 AM gotw42 <got...@gmail.com> wrote:

      val vBox = new VBox {
        children = bufferOfNodes // assign ObservableBuffer to children
This only copies content of  `bufferOfNodes` to `children`
      val addGridPaneButton = new Button {
        text = "Add GridPane to VBox"
        onAction = _ => {
          bufferOfNodes += getNewGridPane // update the ObservableBuffer
You add only to `bufferOfNodes`. This does not impact `vBox.children`.
      content = List(addGridPaneButton, vBox)
Scene `content` is by default a `Group`, so `vBox` is added on top of the button. Once you add content to `vBox.children` it will appear on top the button, so the button will no longer respond. 

To fix that I will add a layout other than Group and assign it to Scene's `root` directly:
```scala
      root = new VBox {
        children ++= Seq(addGridPaneButton, vBox)
      }
```

gotw42

unread,
Jun 1, 2020, 1:59:30 PM6/1/20
to ScalaFX Users
Thank you! This works and makes sense. Sorry for the delayed response.
Reply all
Reply to author
Forward
0 new messages