scalaFX - Titledpane: how do I get the heigth of the content?

108 Views Asked by At

I created a TiteldPane in scalafx

    val titled: TitledPane = new TitledPane()

and put some nodes in it for my GUI. Later I want to read out the heigth of the content of titled.
In javaFX this would be done with:

((Region) titled.getContent()).getHeight()

But if I try to read the height of the content in scala with:

titled.content.height

the height is marked as deprecated and does not compile. I've got a hint to github (scalafx/issue69) that explains why it is deprecated but does not explain how it can be done instead.

Just to clarify: I want to read out the height of the content of the titledpane, not just titled.heigth. When titled is closed, then titled.height is 0, but I want to know what it would be when it is expanded (to detect when it has finished expanding actually).

So, how can I do this in scalafx?

EDIT: Here is a example that shows the described error


    import scalafx.Includes._
    import scalafx.application.JFXApp
    import scalafx.beans.property.DoubleProperty
    import scalafx.beans.value.ObservableValue
    import scalafx.collections.ObservableBuffer
    import scalafx.event.ActionEvent
    import scalafx.scene.Scene
    import scalafx.scene.control.cell.TextFieldListCell
    import scalafx.scene.control.{Button, ListView, TitledPane}
    import scalafx.scene.layout.BorderPane
    
    object TitledPaneEndOfExpansion extends JFXApp {
      val expandedHeight = new DoubleProperty()
      val data: ObservableBuffer[String] = new ObservableBuffer[String]() ++= List("some", "content", "for", "testing")
    
      stage = new JFXApp.PrimaryStage {
        title = "JavaFX: edit after rendering test"
    
        val list: ListView[String] = new ListView[String](data) {
          editable = true
          cellFactory = TextFieldListCell.forListView()
          height.onChange { (source: ObservableValue[Double, Number], oldValue: Number, newValue: Number) =>
            expandedHeight.value = titled.content.height
            println("old height is: " + oldValue.doubleValue() + "   new height is: " + newValue.doubleValue())
            if (newValue.doubleValue() == expandedHeight.value) {
              edit(1)
            }
          }
        }
    
        val titled: TitledPane = new TitledPane {
          text = "titled"
          content = list
        }
    
        scene = new Scene {
          root = new BorderPane {
            center = titled
            bottom = new Button() {
              text = "edit cell 1"
              onAction = { _: ActionEvent => list.edit(1) }
            }
          }
        }
        expandedHeight.value = titled.content.height //set to 400
        list.edit(1)
      }
    }

And here is the buid.sbt file:

name := "JavaFXrenderingProblem"
version := "0.1"
scalaVersion := "2.13.3"
libraryDependencies += "org.scalafx" %% "scalafx" % "15.0.1-R21"
libraryDependencies += "org.controlsfx" % "controlsfx" % "8.40.18"
// Prevent startup bug in JavaFX
fork := true
// Tell Javac and scalac to build for jvm 1.8
javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
scalacOptions += "-target:jvm-1.8"
scalacOptions += "-feature"

This is the message from intelliJ

When I just compile with plain sbt i get the compile error-message:

[info] compiling 1 Scala source to ... JavaFXrenderingProblem\target\scala-2.13\classes ...
[error]  ... JavaFXrenderingProblem\src\main\scala\TitledPaneEndOfExpansion.scala:38:47: value height is not a member of scalafx.beans.property.ObjectProperty[javafx.scene.Node]
[error]         expandedHeight.value = titled.content.height
[error]                                               ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 3 s, completed 03.05.2021 11:09:02
1

There are 1 best solutions below

1
On BEST ANSWER

I actually get two errors when I execute sbt run on your code, and I do not get a deprecation error:

[info] compiling 1 Scala source to C:\Users\SomeUser\src\SFC\target\scala-2.13\classes ...
[error] C:\Users\SomeUser\src\SFX\src\main\scala\TitledPaneEndOfExpansion.scala:23:41: value height is not a member of scalafx.beans.property.ObjectProperty[javafx.scene.Node]
[error]                 expandedHeight.value = titled.content.height
[error]                                                       ^
[error] C:\Users\MichaelAllen\src\SOSFX\src\main\scala\TitledPaneEndOfExpansion.scala:45:40: value height is not a member of scalafx.beans.property.ObjectProperty[javafx.scene.Node]
[error]         expandedHeight.value = titled.content.height //set to 400
[error]                                               ^
[error] two errors found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 3 s, completed May 3, 2021 9:58:00 AM

From your code, the list value returns the contents of the TitledPane instance, titled, as a ListView[String]. It is this object whose height method you're trying to call. Correct?

The primary problem is that the content method of titled doesn't know enough about the type of the object that titled is storing. All it knows is that it is derived from javafx.scene.Node. Such Node instances do not have a height property, and hence your errors. (It's actually a little more complicated than that, but that's the simplest way to explain the issue.)

However, you already have a reference to the object that is the content of titled: list. So you can replace the second reference to titled.content.height with list.height. The first reference, in list's height's onChanged method, is accessible through the source parameter (it identifies the property that changed value, namely list.height in this case). So you can replace title.content.height with source in this case.

I notice that you're using a DoubleProperty type for expandedHeight in your example, but you need to keep looking at the value of the associated types. That's not very idiomatic. If you don't need this value to be reactive, a simple Double would suffice (but this would require that expandedHeight be declared as a var).

Combined, this produces the following code:

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.beans.property.DoubleProperty
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.cell.TextFieldListCell
import scalafx.scene.control.{Button, ListView, TitledPane}
import scalafx.scene.layout.BorderPane

object TitledPaneEndOfExpansion extends JFXApp {
  var expandedHeight: Double = _
  val data: ObservableBuffer[String] = new ObservableBuffer[String]() ++= List("some", "content", "for", "testing")

  stage = new JFXApp.PrimaryStage {
    title = "JavaFX: edit after rendering test"

    val list: ListView[String] = new ListView[String](data) {
      editable = true
      cellFactory = TextFieldListCell.forListView()
      height.onChange { (source: ObservableValue[Double, Number], oldValue: Number, newValue: Number) =>
        expandedHeight = source.value
        println("old height is: " + oldValue.doubleValue() + "   new height is: " + newValue.doubleValue())
        if (newValue.doubleValue() == expandedHeight) {
          edit(1)
        }
      }
    }

    val titled: TitledPane = new TitledPane {
      text = "titled"
      content = list
    }

    scene = new Scene {
      root = new BorderPane {
        center = titled
        bottom = new Button() {
          text = "edit cell 1"
          onAction = { _: ActionEvent => list.edit(1) }
        }
      }
    }
    expandedHeight = list.height.value //set to 400
    list.edit(1)
  }
}

Your code then compiles and runs.

Updated

ScalaFX is simply a wrapper for JavaFX: each JavaFX type has a corresponding ScalaFX type. ScalaFX provides implicit conversion functions to seamlessly convert, say, a JavaFX TitledPane to a ScalaFX TitledPane, and vice versa. However, there's no inheritance relationship between the two sets of objects. That is, a JavaFX TitledPane has no type relationship to a ScalaFX TitledPane. Casting between the two sets of objects is therefore a complicated process.

If you wanted to be able to cast titled.content correctly in order to access the height property of the contents more directly, you would need to get the property's value and explicitly pattern match on the result with the JavaFX version of the object, as follows:

import javafx.scene.control.{ListView => JFXListView}
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.beans.property.DoubleProperty
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.cell.TextFieldListCell
import scalafx.scene.control.{Button, ListView, TitledPane}
import scalafx.scene.layout.BorderPane

object TitledPaneEndOfExpansion extends JFXApp {
  var expandedHeight: Double = _
  val data: ObservableBuffer[String] = new ObservableBuffer[String]() ++= List("some", "content", "for", "testing")

  stage = new JFXApp.PrimaryStage {
    title = "JavaFX: edit after rendering test"

    val list: ListView[String] = new ListView[String](data) {
      editable = true
      cellFactory = TextFieldListCell.forListView()
      height.onChange { (source: ObservableValue[Double, Number], oldValue: Number, newValue: Number) =>
        expandedHeight = titled.content.value match {
          case lv: JFXListView[_] => lv.height.value
          case _ => {
            throw new RuntimeException(s"Unexpected content type: ${titled.content.getClass.getCanonicalName}")
          }
        }
        println("old height is: " + oldValue.doubleValue() + "   new height is: " + newValue.doubleValue())
        if (newValue.doubleValue() == expandedHeight) {
          edit(1)
        }
      }
    }

    val titled: TitledPane = new TitledPane {
      text = "titled"
      content = list
    }

    scene = new Scene {
      root = new BorderPane {
        center = titled
        bottom = new Button() {
          text = "edit cell 1"
          onAction = { _: ActionEvent => list.edit(1) }
        }
      }
    }
    expandedHeight = titled.content.value match {  //set to 400
      case lv: JFXListView[_] => lv.height.value
      case _ => throw new RuntimeException(s"Unexpected content type: ${titled.content.getClass.getCanonicalName}")
    }
    list.edit(1)
  }
}

If you didn't have any other means of referencing the list object, that would be your only option.