ScalaFX-如何使用方法获取场景的标题 [英] ScalaFX - How to get the title of a Scene using a method

查看:65
本文介绍了ScalaFX-如何使用方法获取场景的标题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用ScalaFX并试图了解其工作方式.作为一种技巧(不是我在生产中会做的事情),我想要一个获取窗口标题的方法.

I am using ScalaFX and trying to learn how it works. As an exerpiment (not what I will do in production) I want a method that gets the title of a window.

这是我的Graph.scala文件:

So here is my Graph.scala file:

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类:

Here is my driver object that makes use of this Graph class:

package graphing

import graphing.Graph

object Driver extends App {
  val graph = new Graph

println(graph.getTitle())

  graph.generateChart(args)
}

但是,由于行

println(graph.getTitle())

println(graph.getTitle())

引发的错误:

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

Could someone kindly explain what is going on and how I can achieve my goal here?

推荐答案

此处的问题涉及 JavaFX (因此,是 ScalaFX )初始化.

The problem here concerns JavaFX (and hence, ScalaFX) initialization.

初始化 JavaFX 是一项复杂的业务.(实际上,我直到最近才知道它比我原本认为的还要复杂得多.请参阅最近的答案(请参见 StackOverflow ),以获取更多背景信息.幸运的是,您的问题更容易解决.)

Initializing JavaFX is a complex business. (Indeed, I only recently learned that it was even more complicated than I originally believed it to be. Refer to this recent answer here on StackOverflow for further background. Fortunately, your problem is a little easier to resolve.)

ScalaFX 大大简化了 JavaFX 的初始化,但要求将 JFXApp 特性用作 object .

ScalaFX simplifies JavaFX initialization greatly, but requires that the JFXApp trait be used as part of the definition of an object.

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

JFXApp contains a main method, which must be the starting point of your application; it is this method that takes care of the complexities of initializing JavaFX for you.

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

In your example, you have your Driver object extend scala.App, and so it is App's (and hence, Driver's) main method that becomes the starting point of your own application. This is fine for a regular command line interface (CLI) application, but it cannot be used with ScalaFX/JavaFX applications without a great deal of additional complexity.

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

In your code, JFXApp's main method never executes, because, as it is defined as a class member, it is not the main method of a Scala object, and so is not a candidate for automatic execution by the JVM. You do call it manually from your Graph.generateChart() method, but that method itself is not called until after you try to get the title of the scene, hence the NPE as the stage has not yet been initialized.

如果在 println(graph.getTitle())语句之前将 graph.generateChart(args)调用 怎么办?这样可以解决吗?可悲的是,不.

What if you put the graph.generateChart(args) call before the println(graph.getTitle()) statement? Will that fix it? Sadly, no.

这就是为什么...

JFXApp 还会执行 magic 的另一位功能:它为其 object (以及任何其他 class)执行构造代码由该对象扩展,但不适用于 JavaFX Application Thread ( JAT )上扩展的 trait .这很重要:只有在 JAT 上执行的代码才能与 JavaFX 直接交互(即使通过 ScalaFX 也是如此).如果您尝试在任何其他线程(包括应用程序的主线程)上执行 JavaFX 操作,则将获得异常.

JFXApp also performs one other bit of magic: it executes the construction code for its object (and for any other classes extended by that object, but not for extended traits) on the JavaFX Application Thread (JAT). This is important: only code that executes on the JAT can interact directly with JavaFX (even if through ScalaFX). If you attempt to perform JavaFX operations on any other thread, including the application's main thread, then you will get exceptions.

(此 magic 依赖于已弃用的 Scala 特性 scala.DelayedInit ,该特性已从 Scala库中删除3.0 (也称为 Dotty ),因此将来需要使用其他机制.但是,值得阅读

(This magic relies on a deprecated Scala trait, scala.DelayedInit, which has been removed from the libraries for Scala 3.0, aka Dotty, so a different mechanism will be required in the future. However, it's worth reading the documentation for that trait for further background.)

因此,当 Driver 的构造代码调用 graph.generateChart(args)时,它将导致 JavaFX 初始化,启动 JAT ,并在其上执行 Graph 的构造代码.但是,当 Driver 的构造函数调用仍在主线程上执行的 println(graph.getTitle())时,存在两个问题:

So, when Driver's construction code calls graph.generateChart(args), it causes JavaFX to be initialized, starts the JAT, and executes Graph's construction code upon it. However, by the time Driver's constructor calls println(graph.getTitle()), which is still executing on the main thread, there are two problems:

  1. Graph 的构造代码可能已执行,也可能未执行,因为它正在其他线程上执行.(此问题称为竞赛条件,因为试图调用 println(graph.getTitle())的主线程与 JAT 尝试初始化 graph 实例.)在某些情况下您可能会赢得比赛,但是您也经常会输掉比赛.
  2. 您正在尝试从主线程而不是 JAT JavaFX 进行交互.
  1. Graph's construction code may, or may not, have been executed, as it is being executed on a different thread. (This problem is called a race condition, because there's a race between the main thread trying to call println(graph.getTitle()), and the JAT trying to initialize the graph instance.) You may win the race on some occasions, but you're going to lose quite often, too.
  2. You're trying to interact with JavaFX from the main thread, instead of from the JAT.

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

Here is the recommended approach for your application to work:

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 .虽然我不确定您的应用程序在结构上需要什么样,但是对于您来说这应该是一个不错的起点.

Note that I've combined your Graph class and Driver object into a single object, GraphDriver. While I'm not sure what your application needs to look like architecturally, this should be an OK starting point for you.

还要注意,根本不使用 scala.App .

Note also that scala.App is not used at all.

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

Take care when calling GraphDriver.getTitle(): this code needs to execute on the JAT. The standard workaround for executing any code, that might be running on a different thread, is to pass it by name to scalafx.application.Platform.runLater(). For example:

import scalafx.application.Platform

// ...
Platform.runLater(println(ObjectDriver.getTitle()))
// ...

这篇关于ScalaFX-如何使用方法获取场景的标题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆