尽管使用 fx:ids,FXML 变量仍显示为 null

FXML variables are showing up as null despite using fx:ids

我是初学者,我正在尝试构建一个简单的字典应用程序。

我有一个控制器 class,它有一个按钮可以打开一个新对话框来输入各种详细信息。

对话框由一个单独的控制器控制,这是我遇到问题的地方,我的 FXML 元素被赋予 null 值,因此当我 运行 代码时,我得到一个 nullpointerexception。我用@FXML 注释了 FXML 变量,并检查了 fxml 文件中的 fx:id 是否与 java 文件中的相匹配。

这里是Controller.java代码:

package sample;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.BorderPane;
import javafx.scene.control.Dialog;

import java.io.IOException;
import java.util.Optional;

public class Controller {

@FXML
private BorderPane mainBorderPane;
@FXML
private DialogController controller = new DialogController();
@FXML
public void initialize(){

}

@FXML
public void newItemDialog(){
    Dialog <ButtonType> dialog = new Dialog<>();
    dialog.initOwner(mainBorderPane.getScene().getWindow());
    dialog.setTitle("Insert Word");
    FXMLLoader fxmlLoader = new FXMLLoader();
    fxmlLoader.setLocation(getClass().getResource("newDialog.fxml"));
    try{
        dialog.getDialogPane().setContent(fxmlLoader.load());
    }catch(IOException e) {
        e.printStackTrace();
        return;
    }
    dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
    dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
    Optional<ButtonType> result = dialog.showAndWait();
    if(result.isPresent() && result.get() == ButtonType.OK){
        boolean results = controller.processResults();

    }else{
        System.out.println("Cancelled");
        return;
    }
}
}

对话框Controller.java代码如下:

package sample;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import sample.control.Data;

import java.io.IOException;
import java.util.Optional;

public class DialogController {
@FXML
private TextField nameField;
@FXML
private TextField descriptionField;
@FXML
private TextField typeField;
@FXML
private TextField sentenceField;


@FXML
public boolean processResults() {

    String name = nameField.getText();
    String description = descriptionField.getText();
    String type = typeField.getText();
    String sentence = sentenceField.getText();

    return Data.getInstance().addWord(name,type,description,sentence);
    }    
}

这里是sample.fxml代码:

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ListView?>
<?import java.lang.String?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.VBox?>
<BorderPane fx:controller="sample.Controller"
        xmlns:fx="http://javafx.com/fxml"
        fx:id ="mainBorderPane">

<top>
    <MenuBar>

        <Menu fx:id="fileButton" text="File">
            <MenuItem fx:id="newButton" onAction="#newItemDialog" text="New"/>
            <MenuItem fx:id="updateButton" text="Update"/>
            <MenuItem fx:id="deleteButton" text="Delete"/>
            <MenuItem fx:id="saveButton" text="Save"/>
        </Menu>
        <Menu fx:id="viewButton" text="View">
            <MenuItem text="Dark Mode"/>
        </Menu>
    </MenuBar>
</top>
<left>
    <ListView fx:id="wordListView" BorderPane.alignment="TOP_LEFT" prefHeight="Infinity" prefWidth="250">

    </ListView>
</left>
<center>
    <VBox>
        <TextArea fx:id = "wordDetails" prefWidth="Infinity" prefHeight="400">

        </TextArea>
        <TextArea prefWidth="Infinity" prefHeight="200">

        </TextArea>
    </VBox>
</center>

</BorderPane>

这里是newDialog.fxml代码:

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

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

<?import javafx.scene.text.Text?>
<?import javafx.scene.control.DialogPane?>
<DialogPane xmlns="http://javafx.com/javafx"
        xmlns:fx="http://javafx.com/fxml"
        fx:controller="sample.DialogController"
        prefHeight="400.0" prefWidth="600.0">
<headerText>
    Enter A Word
</headerText>
<content>
    <VBox>
        <TextField fx:id="nameField" promptText="Enter the word"/>
        <TextField fx:id="typeField" promptText="Enter the type"/>
        <TextField fx:id="descriptionField" promptText="Enter a description"/>
        <TextField fx:id="sentenceField" promptText="Enter a sentence"/>
    </VBox>
</content>

提前致谢!

@FXML 注释指示应由 FXMLLoader 初始化的字段,以引用在加载 FXML 文件时创建的与 FXML 文件中的元素相对应的对象。因此:

  1. 只有当字段对应于 FXML 文件中的元素时,注释字段 @FXML 才有意义
  2. 从不初始化一个字段如果它被注释 @FXML 是有意义的(因为 FXMLLoader 应该初始化它)

你的 DialogController 字段

@FXML
private DialogController controller = new DialogController();

由于以下两个原因没有意义:sample.fxml 中没有 DialogController 元素,如果有,则初始化该字段没有意义。

控制器是与从 FXML 文件加载的 UI 关联的 特定对象 FXMLLoader 在加载 FXML 文件时建立关联。如果您多次加载 FXML 文件(正如您在此处所做的那样,因为您正在事件处理程序中加载 newDialog.fxml),那么(当然)您每次都会获得 FXML 中所有元素的新实例,因此每次都有一个新的控制器实例 class。

您使用

创建的对象
@FXML private DialogController controller = new DialogController();

不是您加载 newDialog.fxml 时任何 UI 加载的控制器;它只是同一个 class 的另一个对象。目前尚不清楚您是否希望此字段以某种方式引用用户第一次从菜单中选择 "New" 时创建的控制器,或者他们第二次选择该菜单项时创建的控制器,等等。或者,当然,在加载 newDialog.fxml.

之前初始化它时,您会如何期望它引用这些控制器中的任何一个

加载 FXML 后,您可以从 FXMLLoader 中获取控制器。所以你只需要:

public class Controller {

    @FXML
    private BorderPane mainBorderPane;
    @FXML
    public void initialize(){

    }

    @FXML
    public void newItemDialog(){
        Dialog <ButtonType> dialog = new Dialog<>();
        dialog.initOwner(mainBorderPane.getScene().getWindow());
        dialog.setTitle("Insert Word");
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("newDialog.fxml"));
        try{
            dialog.getDialogPane().setContent(fxmlLoader.load());
        }catch(IOException e) {
            e.printStackTrace();
            return;
        }
        dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
        dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
        Optional<ButtonType> result = dialog.showAndWait();

        DialogController controller = fxmlLoader.getController();

        if(result.isPresent() && result.get() == ButtonType.OK){
            boolean results = controller.processResults();

        }else{
            System.out.println("Cancelled");
            return;
        }
    }

}