【问题标题】:ScalaFX - How to get the title of a Scene using a methodScalaFX - 如何使用方法获取场景的标题
【发布时间】:2020-09-18 10:24:44
【问题描述】:

我正在使用 ScalaFX 并尝试了解它的工作原理。作为一个实验(不是我将在生产中做的),我想要一个获取窗口标题的方法。

这是我的 Graph.scala 文件:

package graphing

import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.paint.Color

class Graph {
  val app = new JFXApp {
    stage = new JFXApp.PrimaryStage {
      title = "First GUI"
      scene = new Scene {
        fill = Color.Coral
      }
    }
  }

  def getTitle() = {
    app.stage.getTitle
  }

  def generateChart(args: Array[String]) = app.main(args)
}

这是我使用此 Graph 类的驱动程序对象:

package graphing

import graphing.Graph

object Driver extends App {
  val graph = new Graph

println(graph.getTitle())

  graph.generateChart(args)
}

但是,这行不通

println(graph.getTitle())

抛出的错误:

有人能解释一下发生了什么以及我如何在这里实现我的目标吗?

【问题讨论】:

    标签: scala user-interface javafx scalafx


    【解决方案1】:

    这里的问题涉及 JavaFX(以及因此,ScalaFX)的初始化。

    初始化JavaFX 是一项复杂的工作。 (事实上​​,我最近才知道它甚至比我最初认为的更复杂。有关更多背景信息,请参阅 StackOverflow 上的this recent answer。幸运的是,您的问题更容易解决。)

    ScalaFX 极大地简化了 JavaFX 初始化,但需要将 JFXApp 特征用作 object 定义的一部分。

    JFXApp 包含一个main 方法,必须是您应用程序的起点;正是这种方法为您解决了初始化 JavaFX 的复杂性。

    在您的示例中,您的 Driver 对象扩展了 scala.App,因此它是 App 的(因此,Driver 的)main 方法成为您自己的起点应用。这对于常规 命令行界面 (CLI) 应用程序来说很好,但它不能与 ScalaFX/JavaFX一起使用> 应用程序没有太多额外的复杂性。

    在您的代码中,JFXAppmain 方法永远不会执行,因为它被定义为类成员,它不是 Scala 的 main 方法 @987654335 @,因此不是 JVM 自动执行的候选对象。您确实从您的 Graph.generateChart() 方法手动调用它,但是直到 您尝试获取场景的标题之后才调用该方法本身,因此 NPE 作为阶段尚未初始化。

    如果您将graph.generateChart(args) call 放在 println(graph.getTitle()) 语句之前会怎样?那会修复它吗?很遗憾,没有。

    这就是为什么...

    JFXApp 还执行另一位魔术:它执行其object 的构造代码(以及由该对象扩展的任何其他classes,但不适用于扩展@ JavaFX 应用程序线程 (JAT) 上的 987654342@s)。这很重要:只有在 JAT 上执行的代码才能直接与 JavaFX 交互(即使通过 ScalaFX)。如果您尝试在任何其他线程(包括应用程序的主线程)上执行 JavaFX 操作,则会出现异常。

    (这个 magic 依赖于已弃用的 Scala 特征 scala.DelayedInit,该特征已从 Scala 3.0 的库中删除,又名Dotty,因此未来将需要不同的机制。但是,值得阅读 the documentation 以了解更多背景知识。)

    所以,当Driver的构造代码调用graph.generateChart(args)时,会导致JavaFX被初始化,启动JAT,并执行Graph的施工规范。但是,当Driver的构造函数调用println(graph.getTitle())时,仍然在主线程上执行,有两个问题:

    1. Graph 的构造代码可能已执行,也可能未执行,因为它正在不同的线程上执行。 (这个问题被称为竞态条件,因为在试图调用println(graph.getTitle()) 的主线程和试图初始化graph 实例的JAT 之间存在竞态。 ) 您可能会在某些情况下赢得比赛,但也经常会输。
    2. 您正尝试从主线程而不是 JATJavaFX 交互。

    以下是您的应用程序运行的推荐方法:

    package graphing
    
    import scalafx.application.JFXApp
    import scalafx.scene.Scene
    import scalafx.scene.paint.Color
    
    object GraphDriver
    extends JFXApp {
    
      // This executes at program startup, automatically, on the JAT.
      stage = new JFXApp.PrimaryStage {
        title = "First GUI"
        scene = new Scene {
          fill = Color.Coral
        }
      }
    
      // Print the title. Works, because we're executing on the JAT. If we're NOT on the JAT,
      // Then getTitle() would need to be called via scalafx.application.Platform.runLater().
      println(getTitle())
    
      // Retrieve the title of the stage. Should equal "First GUI".
      //
      // It's guaranteed that "stage" will be initialized and valid when called.
      def getTitle() = stage.title.value
    }
    

    请注意,我已将您的Graph 类和Driver 对象组合成一个对象GraphDriver。虽然我不确定您的应用程序在架构上需要什么样的外观,但这对您来说应该是一个不错的起点。

    还要注意scala.App 根本没有使用。

    调用GraphDriver.getTitle() 时要小心:此代码需要在JAT 上执行。执行任何可能在不同线程上运行的代码的标准解决方法是将其按名称传递给scalafx.application.Platform.runLater()。例如:

    import scalafx.application.Platform
    
    // ...
    Platform.runLater(println(ObjectDriver.getTitle()))
    // ...
    

    【讨论】:

    • 感谢您的帮助 - 这真是太有见地了!
    • 您好,刚刚跟进这个问题 - 假设我想构建一个基于 ScalaFX 的可视化包。我想使用这个包来做:导入 myPackage 运行一些代码生成数据 myPackage.plot(data) 即我的包的运行发生在我选择的任何类中,而不仅仅是扩展 JFXApp 的类。在我看来,由于 JFXApp 始终是起点,我不能这样做。我说的对吗?
    • @finite_diffidence 不知道你到底想做什么,我帮不了太多。也许您应该问另一个带有必要细节的问题?但是,我看不出有任何理由说明您不应该使用 ScalaFX,或者从 JFXApp 开始会阻止您做您需要做的事情。
    • 好的,谢谢,我会发布一个新问题。我真的很想用ScalaFX,但我只是一个菜鸟哈哈
    猜你喜欢
    • 2014-12-28
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-11
    • 1970-01-01
    相关资源
    最近更新 更多