【问题标题】:Can't instantiate class fields in JavaFX project无法在 JavaFX 项目中实例化类字段
【发布时间】:2015-09-30 14:27:27
【问题描述】:

所以我试图从 Swing 迁移到 JavaFX,我只是声明了 start()launch() 方法,而不知道它们是如何工作的。但是下面的代码会在控制台上打印 false 和 false。但是,当我单击 GUI 中的按钮时,使用 Scene Builder 构建,执行 myMethod(),这次它打印为 true。为什么说primaryStage 没有实例化?

补充资料: 出于同样的原因,我还将这个类设为我的控制器 - 它需要访问舞台参考。我没有发布的完整版 Main 实现了 Initializable,如果这很重要的话。

作为附加问题我想知道我是否需要 primaryStage 的字段来引用 myMethod() 中的 Application Stage,这将只有一个?

public class Main extends Application {

    private Stage primaryStage;

    public void start(Stage primaryStage) {
        this.primaryStage = primaryStage;
        try {
            Scene scene = new Scene(FXMLLoader.load(getClass().getResource("Sample.fxml")),600,400);
            primaryStage.setScene(scene);
        } catch(Exception e) {
            e.printStackTrace();
        }
        primaryStage.show();
        //both lines below print false; As they should.
        System.out.println(this.primaryStage == null);
        myMethod();
    }

    public static void main(String[] args) {
        launch(args);
    }

    public void myMethod() {
        System.out.println(primaryStage == null);
    }
}

编辑

将此 FXML 文档放在与上述类相同的文件夹中,您可以运行 Main 以查看该按钮确实打印为 true。

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<HBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Main">
   <children>
      <Button mnemonicParsing="false" onAction="#myMethod" text="Button" />
   </children>
</HBox>

【问题讨论】:

  • 你能展示你如何在Button和MyMethod之间创建监听器关系吗?并请出示控制器。
  • 你说Main实现Initializable?你为什么从这段代码中删除它?
  • @JamesWierzba 为了简单起见,我删除了似乎不必要的部分。该关系在 FXML 文档onAction="#myMethod" 中创建,并且该方法也正确执行。只是结果令人惊讶
  • 您删除的代码很可能与问题有关。此外,用户可能希望重新创建您的问题以尝试调试它,如果您不显示代码,这是不可能的。
  • 不要使用Application 子类作为控制器:它不会让任何事情变得更简单。 launch() 方法实例化Application 子类并调用start(...) 方法。如果您在fx:controller 属性中指定相同的类,FXMLLoader 将创建同一类的新实例,并将@FXML-annotated 字段注入到该实例。当您按下按钮时,将调用FXMLLoader 创建的实例上的处理程序方法,而不是调用launch() 创建的实例上的处理方法。

标签: java javafx scenebuilder


【解决方案1】:

这是因为FXMLLoader 将创建application.Main 类的新实例,其中start() 方法未被调用,因此private Stage primaryStage 为空。

FXML最好把主类和控制器分开,必要时再通过初级阶段:

...
try
{
    FXMLLoader loader = new FXMLLoader( getClass().getResource( "Sample.fxml" ) );
    Scene scene = new Scene( loader.load(), 600, 400 );

    ( (MyController) loader.getController() ).setPrimaryStage(primaryStage);
    primaryStage.setScene( scene );
}
catch ( Exception e )
{
    e.printStackTrace();
}
...

MyController 类可以很简单:

public class MyController {

    private Stage primaryStage;

    public void setPrimaryStage(Stage primaryStage) {
        this.primaryStage = primaryStage;
    }
}

还可以实现Initializable接口。参考Introduction to FXML :: Controllers

另请注意,您可以通过以下方式从属于该构建的场景图(即显示的阶段)的任何节点获取场景及其阶段:

Scene scene = anynode.getScene();
Stage primaryStage = (Stage) anynode.getScene().getWindow();

当然,对于您创建的次要阶段,getWindow() 将返回该阶段而不是主要阶段。

【讨论】:

  • 这很有意义。但是如何在我单独定义的控制器中引用 Stage 对象呢?
  • 我不知道控制器被定义为MyController 类?我的控制器类是否需要扩展 MyController 以支持 getPrimaryStage() 函数?
  • @user2651804 抱歉不清楚! MyController 是 your 类 :),它有一个 initialize() 方法(在我们的例子中,当然还有 primaryStage 的设置器)。类似this
  • 你为什么还要尝试在控制器中创建对它的引用?当您需要访问任何节点时,只需在任何节点上调用getScene().getWindow()
  • @James_D 这就是为什么我在答案中提到“如果有必要”。但会将您的注释添加到答案中。
【解决方案2】:

根本不需要跳过任何这些箍来进入窗口。您可以在任何节点上调用getScene().getWindow() 以获取显示它的窗口(当然您可以以通常的方式将任何节点注入您的控制器)。

不要使用Application 子类作为控制器类:您将拥有(至少)两个不同的实例(一个由launch() 方法创建,因为它是Application 子类,另一个由FXMLLoader,因为它是控制器类)。在不同的实例中会初始化不同的字段。

创建一个控制器类,并在其中注入至少一个节点:

public class Controller {

    @FXML
    private Parent root ;

    @FXML
    private void myMethod() {
        Window window = root.getScene().getStage();
        // assuming you are running as a standalone application, the window 
        // will actually be a Stage instance.

        window.hide();  // for example...
    }
}

将其用作您的控制器类并将节点注入其中:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<HBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller" fx:id="root">
   <children>
      <Button mnemonicParsing="false" onAction="#myMethod" text="Button" />
   </children>
</HBox>

【讨论】:

  • 你认为引用根节点而不是舞台本身是更好的做法吗?从我的角度来看,似乎没有区别。
  • 是的,但这只是因为它避免了与控制器类的所有丑陋连接。请注意,我不需要从加载器中获取控制器并在其上调用方法等。每当我需要从加载器中获取控制器时,我都认为这是我的设计出现问题的标志(尽管有当然是比其他方法更容易的情况)。应用程序代码应该只加载 UI 组件并使用它做一些事情:它不需要担心 UI 组件是如何被控制的。再说一次,您还没有注入 something 的次数很少。
  • @user2651804,可能存在有初级阶段的场景,但该控制器的fxml不是其中的一部分。即 2 个不同的阶段,我们需要 getWindow() 不返回的阶段。但这也将是“代码气味”。
  • @user2651804,最终当应用程序变得复杂时,您将拥有多个控制器。并且将需要在这些控制器中传输数据。对于 SO here 上的类似问题,已经发布了不同的方法。在这些情况下,您可能希望从 FXMLloader 获取控制器,甚至在某些地方使用 fxmlloadler.setController() 选项。
  • @UlukBiy 我得出的结论是,一旦你达到那个点,一个 D.I.为了保持设计的优雅,框架是值得拥有的。使用afterburner.fx,这是一个面向FX的D.I.框架,或使用标准框架:here's 我的 Spring 示例。但是这个问题有点离题:)。
猜你喜欢
  • 2019-12-04
  • 2014-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-31
  • 2022-01-03
  • 1970-01-01
  • 2013-06-15
相关资源
最近更新 更多