ScalaFX - 如何使用方法获取场景的标题

2024-01-04

我正在使用 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())

The error that is thrown: enter image description here

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


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

正在初始化JavaFX是一项复杂的业务。 (确实,我最近才知道这是甚至更复杂比我最初相信的要多。参考最近的这个答案 https://stackoverflow.com/a/62001855/2593574在这里堆栈溢出了解更多背景。幸运的是,您的问题更容易解决。)

ScalaFX简化JavaFX初始化很大,但要求JFXApp特征被用作定义的一部分object.

JFXApp包含一个main方法,其中must是您申请的起点;正是这个方法解决了初始化的复杂性JavaFX为你。

在你的例子中,你有你的Driver对象扩展scala.App, 就是这样App的(因此,Driver's) main方法成为您自己的应用程序的起点。这对于普通人来说很好命令行界面 (CLI) 应用程序,但不能与ScalaFX/JavaFX应用程序无需太多额外的复杂性。

在你的代码中,JFXApp's main方法永远不会执行,因为它被定义为类成员,所以它不是main的方法Scala object,因此不是自动执行的候选者JVM。您确实可以从您的Graph.generateChart()方法,但该方法本身不会被调用,直到after你试图获得场景的标题,因此NPE因为阶段尚未初始化。

如果你把graph.generateChart(args) call before the println(graph.getTitle())陈述?这能解决问题吗?可悲的是没有。

这就是为什么...

JFXApp还执行另一位magic:它执行其构造代码object(以及对于任何其他classes 由该对象扩展,但不用于扩展traits) 在JavaFX 应用程序线程 (JAT)。这很重要:只有在JAT可以直接与JavaFX(即使通过ScalaFX)。如果您尝试执行JavaFX在任何其他线程(包括应用程序的主线程)上进行操作,那么您将得到异常。

(This magic依赖于已弃用的Scala trait, scala.DelayedInit,已从库中删除斯卡拉3.0, aka Dotty,因此将来需要不同的机制。不过,值得一读文档 https://www.scala-lang.org/api/current/scala/DelayedInit.html了解该特征以了解更多背景。)

所以,当Driver的构造代码调用graph.generateChart(args),它导致JavaFX要初始化,启动JAT,并执行Graph的构造代码。然而,到了那时Driver的构造函数调用println(graph.getTitle()),仍然在主线程上执行,有两个问题:

  1. Graph的构造代码可能已执行,也可能未执行,因为它是在不同的线程上执行的。 (这个问题被称为竞争条件,因为主线程尝试调用之间存在竞争println(graph.getTitle()),以及JAT尝试初始化graph例如。)你可能会在某些情况下赢得比赛,但你也会经常失败。
  2. 您正在尝试与以下人员互动JavaFX从主线程,而不是从JAT.

以下是为您的应用程序工作推荐的方法:

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。执行可能在不同线程上运行的任何代码的标准解决方法是将其传递给by name to scalafx.application.Platform.runLater()。例如:

import scalafx.application.Platform

// ...
Platform.runLater(println(ObjectDriver.getTitle()))
// ...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ScalaFX - 如何使用方法获取场景的标题 的相关文章

随机推荐