ScalaFX - 如何使用方法获取场景的标题
ScalaFX - How to get the title of a Scene using a method
我正在使用 ScalaFX 并尝试了解它的工作原理。作为一个实验(不是我将在生产中做的)我想要一个获得 window.
标题的方法
这是我的 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)
}
这是我的 driver object,它使用了这个图表 class:
package graphing
import graphing.Graph
object Driver extends App {
val graph = new Graph
println(graph.getTitle())
graph.generateChart(args)
}
但是,由于
行,这不起作用
println(graph.getTitle())
抛出的错误:
有人可以解释一下发生了什么以及我如何才能在这里实现我的目标吗?
这里的问题涉及 JavaFX(因此,ScalaFX)初始化。
初始化 JavaFX 是一项复杂的工作。 (事实上 ,我最近才知道它 甚至比我最初认为的还要复杂 。请参阅 Whosebug[=119= 上的 this recent answer ] 了解更多背景知识。幸运的是,您的问题更容易解决。)
ScalaFX 极大地简化了 JavaFX 初始化,但要求 JFXApp
特征被用作定义的一部分object
.
JFXApp
包含一个main
方法,必须是你应用程序的起点;正是这种方法为您解决了初始化 JavaFX 的复杂性。
在您的示例中,您的 Driver
object 扩展了 scala.App
,因此它是 App
的(因此,Driver
' s) main
方法成为您自己的应用程序的起点。这对于常规 命令行界面 (CLI) 应用程序来说很好,但它不能与 ScalaFX/JavaFX 应用程序没有大量额外的复杂性。
在您的代码中,JFXApp
的 main
方法永远不会执行,因为它被定义为 class 成员,它不是 main
方法Scala object
,因此不是 JVM 自动执行的候选者。您确实从 Graph.generateChart()
方法中手动调用它,但是直到 在 您尝试获取场景的标题之后才会调用该方法本身,因此 NPE 因为舞台尚未初始化。
如果将 graph.generateChart(args)
调用 放在 println(graph.getTitle())
语句之前会怎样?那会解决吗?很遗憾,没有。
这就是为什么...
JFXApp
还执行另一位 magic:它执行其 object
的构造代码(以及任何其他 class
es 由 object 扩展,但不用于 JavaFX 应用程序线程 (JAT) 上的扩展 trait
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())
时,它仍然在主线程上执行,有两个问题:
Graph
的构造代码可能已执行,也可能未执行,因为它正在不同的线程上执行。 (此问题称为 race condition,因为尝试调用 println(graph.getTitle())
的主线程与尝试调用 JAT 的主线程之间存在竞争初始化 graph
实例。)在某些情况下你可能会赢得比赛,但你也经常会输。
- 您正在尝试从主线程而不是从 JAT.
与 JavaFX 交互
以下是您的应用程序工作的推荐方法:
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
class 和 Driver
object 合并为一个 object、GraphDriver
。虽然我不确定您的应用程序在架构上需要什么样的外观,但这对您来说应该是一个不错的起点。
另请注意,根本没有使用 scala.App
。
调用GraphDriver.getTitle()
时要小心:此代码需要在JAT上执行。执行任何代码的标准解决方法,可能是 运行 在不同的线程上,是将它 按名称 传递给 scalafx.application.Platform.runLater()
。例如:
import scalafx.application.Platform
// ...
Platform.runLater(println(ObjectDriver.getTitle()))
// ...
我正在使用 ScalaFX 并尝试了解它的工作原理。作为一个实验(不是我将在生产中做的)我想要一个获得 window.
标题的方法这是我的 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)
}
这是我的 driver object,它使用了这个图表 class:
package graphing
import graphing.Graph
object Driver extends App {
val graph = new Graph
println(graph.getTitle())
graph.generateChart(args)
}
但是,由于
行,这不起作用println(graph.getTitle())
抛出的错误:
有人可以解释一下发生了什么以及我如何才能在这里实现我的目标吗?
这里的问题涉及 JavaFX(因此,ScalaFX)初始化。
初始化 JavaFX 是一项复杂的工作。 (事实上 ,我最近才知道它 甚至比我最初认为的还要复杂 。请参阅 Whosebug[=119= 上的 this recent answer ] 了解更多背景知识。幸运的是,您的问题更容易解决。)
ScalaFX 极大地简化了 JavaFX 初始化,但要求 JFXApp
特征被用作定义的一部分object
.
JFXApp
包含一个main
方法,必须是你应用程序的起点;正是这种方法为您解决了初始化 JavaFX 的复杂性。
在您的示例中,您的 Driver
object 扩展了 scala.App
,因此它是 App
的(因此,Driver
' s) main
方法成为您自己的应用程序的起点。这对于常规 命令行界面 (CLI) 应用程序来说很好,但它不能与 ScalaFX/JavaFX 应用程序没有大量额外的复杂性。
在您的代码中,JFXApp
的 main
方法永远不会执行,因为它被定义为 class 成员,它不是 main
方法Scala object
,因此不是 JVM 自动执行的候选者。您确实从 Graph.generateChart()
方法中手动调用它,但是直到 在 您尝试获取场景的标题之后才会调用该方法本身,因此 NPE 因为舞台尚未初始化。
如果将 graph.generateChart(args)
调用 放在 println(graph.getTitle())
语句之前会怎样?那会解决吗?很遗憾,没有。
这就是为什么...
JFXApp
还执行另一位 magic:它执行其 object
的构造代码(以及任何其他 class
es 由 object 扩展,但不用于 JavaFX 应用程序线程 (JAT) 上的扩展 trait
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())
时,它仍然在主线程上执行,有两个问题:
Graph
的构造代码可能已执行,也可能未执行,因为它正在不同的线程上执行。 (此问题称为 race condition,因为尝试调用println(graph.getTitle())
的主线程与尝试调用 JAT 的主线程之间存在竞争初始化graph
实例。)在某些情况下你可能会赢得比赛,但你也经常会输。- 您正在尝试从主线程而不是从 JAT. 与 JavaFX 交互
以下是您的应用程序工作的推荐方法:
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
class 和 Driver
object 合并为一个 object、GraphDriver
。虽然我不确定您的应用程序在架构上需要什么样的外观,但这对您来说应该是一个不错的起点。
另请注意,根本没有使用 scala.App
。
调用GraphDriver.getTitle()
时要小心:此代码需要在JAT上执行。执行任何代码的标准解决方法,可能是 运行 在不同的线程上,是将它 按名称 传递给 scalafx.application.Platform.runLater()
。例如:
import scalafx.application.Platform
// ...
Platform.runLater(println(ObjectDriver.getTitle()))
// ...