JavaFX的。试图在控制器中实例化一个阶段不会检索它内部的值?还是它?

时间:2015-08-18 14:48:57

标签: java nullpointerexception javafx-8

环境:使用Scene Builder的Java FX 8

上下文:我尝试使用TableView创建一个包含TableColumnObservableList的简单阶段。调用create_ConfigStage()来创建阶段,并初始化initialize()。 FXML的控制器类是ControllerA(这个类),一切都很好,直到...

问题:无法弄清楚为什么在words.size中调用create_ConfigStage()会导致NullPointerException(或0),但在initialize()中调用相同内容会产生问题。

问题类型:java.lang.NullPointerException或在initialized()中初始化的精确值未在创建阶段后初始化(或类似内容)。

仅供参考:没有其他内容混淆words变量甚至templates变量。

附加测试:

  1. 只需添加按钮即可使用buttonhandleButton来测试单词的大小。结果还可以。但仍然在同一个旧地方无效。

  2. 只需添加字符串testVal即可查看值是否有所变化。显然,它没有。

    class ControllerA {
    
    @FXML TableView<Words> templatesTable;
    @FXML TableColumn<Words, String> templateHeaders;
    @FXML Button button;
    
    Stage stage;
    ObservableList<Words> templates;
    List<Words> words = new ArrayList<Words>();
    String testVal = "Nothing Happened";
    
    @FXML
    public void initialize(){
        words = FileUtil.readMacroFile(new File("macros.dat"));
        templates = FXCollections.observableArrayList();
        templates.addAll(words);
        templatesTable.setItems(templates);
        templateHeaders.setCellValueFactory(new PropertyValueFactory<Words, String>("header"));
        testVal = "Something Happened";
    
        System.out.println(words.size()); //<<-- size = 5 (THIS IS PRINTED)
    }
    
    public void create_ConfigStage() {
        stage = new Stage();
        FXMLLoader loader = new FXMLLoader(ClassLoader.getSystemResource("windows/configureTemplates.fxml"));
        AnchorPane root = null;
    
        try {
            root = loader.load();
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.showAndWait();
        } catch (IOException e) {
            e.printStackTrace();
        }
    
        System.out.println(testVal); //This one prints "Nothing Happened" instead of "Something happened"!!!
        System.out.println(words.size()); //<<-- The line showing null pointer(if wasnt initialized in initializer() else shows 0)
     }
    
    @FXML
    public void handleButton(){
        System.out.println(words.size()); // <<-- this one prints 5(THIS IS PRINTED AS WELL)
    }
    
  3. 调用create_ConfigStage()

    的代码
    public class Main_Controller {
    @FXML Button createConfig;
    
    @FXML
        public void handleConfigTemplateRequest(){
            main.getControllerA().create_ConfigStage();
            //This method is invoked by "createConfig" button
        }
    }
    

    这是调用的类创建主要阶段

    public class Main extends Application{
    
    public void start(Stage mainStage) throws Exception{
        this.primaryStage = mainStage;
        mainStage.setTitle("Scratch GLSL Editor");
    
        FXMLLoader loader = new FXMLLoader(ClassLoader.getSystemResource("main.fxml"));   //main.fxml's controller is class Main_Controller
        AnchorPane root = loader.load();
    
        Scene mainScene = new Scene(root);
        mainStage.setScene(mainScene);
        mainStage.show();
    
        ControllerA controllerA = new ControllerA();
        controllerA.setMain(this); //to get the controller from this class
        setControllerA(controllerA);
    
    }
    
    //setter and getter for ControllerA here
    
    public static void main(String[] args){
         launch(args);
    }
    }
    

3 个答案:

答案 0 :(得分:2)

您调用ControllerA的{​​{1}}实例由行

创建
create_ConfigStage()

换句话说,它不是作为ControllerA controllerA = new ControllerA(); load(...)进程的一部分为您创建的。因此,任何FXMLLoader注入的字段都不会被初始化,并且永远不会在该实例上调用FXML方法。

稍后,当您加载FXML时,initialize()会创建另一个 FXMLLoader实例,并且ControllerA进程的一部分会调用load(...) } 那个实例。按下该按钮时,将在同一initialize()创建的实例上调用事件处理程序方法。

因此,FXMLLoader已在words创建的实例中的FileUtil.readMacroFile(...)方法中正确初始化,但在您自己创建的实例中从未正确初始化。因此,对于您调用FXMLLoader的实例,create_ConfigStage()是您在声明中内联创建的空words(如果省略ArrayList,则为null宣言)。

由于您还没有真正展示FXML文件与各种控制器之间的关系,但是要获取由其加载的控制器实例,因此您并未充分清楚自己要做什么。 = new ArrayList<Words>()你会使用像

这样的代码
FXMLLoader

使用FXMLLoader loader = new FXMLLoader(ClassLoader.getSystemResource("windows/configureTemplates.fxml")); AnchorPane root = null; try { root = loader.load(); Scene scene = new Scene(root); stage.setScene(scene); stage.showAndWait(); ControllerA controllerA = loader.getController(); System.out.println(controllerA.getWords().size()); } catch (IOException e) { e.printStackTrace(); } 中定义的合适getWords()方法:

ControllerA

显然你无法在public List<Words> getWords() { return words ; } 的实例方法中执行此操作,因为你创建了鸡与蛋的情况(你需要从ControllerA获取控制器实例,但是在你拥有控制器实例之前,你不会调用FXMLLoader

您遇到问题的原因是您试图反转通常的FXML加载过程。通常的过程是这样的:

  1. 创建FXMLLoader
  2. 致电FXMLLoader
  3. 上的load(...)
  4. FXMLLoader读取FXML文件
  5. FXMLLoader创建控制器,由FXML文件中的FXMLLoader属性指定
  6. 换句话说,FXML文件(视图)会为您实例化控制器。

    您正在尝试以相反的方式执行操作,即您尝试让控制器实例创建fx:controller并随后加载视图。你可以用这种方式工作,但你必须以不同的方式进行设置。以下假设您没有在其他地方加载此FXML文件(您至少必须相应地修改该代码)。

    首先,FXMLLoader 中移除fx:controller属性(因为您要在创建configureTemplates.fxml之前创建控制器,否则您不会...我希望加载器再次实例化控制器类。

    然后按如下方式修改FXMLLoader方法:

    create_ConfigStage()

    您可能还想阅读this related technique

答案 1 :(得分:0)

我认为这一行使得单词为null:

 words = FileUtil.readMacroFile()

因此,您需要在FileUtil中检查该方法,以查看它何时返回null。

答案 2 :(得分:0)

在尝试构建通过FXML文件定义内容的高级对话框时,我遇到了类似的问题。

对我有用的是:

  • 将对话框内容定义为FXML文件,包括其控制器的声明
  • 在控制器中,将对话框定义为从实用程序类加载的私有静态成员(通过static关键字可以在完全构建控制器之前实例化该对话框)
  • 将控制器构建为单例,实例由FXML加载器构建控制器时调用的initialize()方法初始化

请注意不要在initialize()方法中使用该对话框,因为该对话框尚未准备好,我花了一些时间弄清楚为什么,James_D的回答对我有很大帮助!

对话框的FXML定义:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="popup" fx:id="mainPane"
        xmlns="http://javafx.com/javafx/8"
        xmlns:fx="http://javafx.com/fxml/1"
        fx:controller="org.example.controllers.MyDialogController"/>

对话框的控制器类:

public class MyDialogController implements Initializable {
    private static ObservationTactileDialogController instance;

    // FxmlDialog.java is a custom class extending Dialog<>
    // and provides a loader
    private static FxmlDialog<LinkedList<ObservationDTO>> dialog =
        new FxmlDialog<>("/fxml/dialog.fxml");

    private ResultType result;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        instance = this;
        // do your own stuff here...
    }

    public ResultType edit() {
        dialog.getDialogStage().showAndWait();

        return result;
    }

    public MyDialogController getInstance() {
        return instance;
    }

    // some business logic to built the result
}

然后,在任何其他控制器方法中,您都可以像下面这样使用此对话框:

MyDialogController dialogController = MyDialogController.getInstance();
ResultType result = dialogController.edit();

我希望这会有所帮助!