java - Controller 中的所有 JavaFX FXML 对象均为 null

标签 java javafx fxml

我意识到这个问题以前曾被问过,但没有一个解决方案对我有用。我从我的 Controller 启动一个线程,然后该线程从我的数据库获取一些数据。该线程将数据发送到我在 Controller 中实现的接口(interface)。当我尝试从那里访问我的任何 JavaFX 元素时,出现空指针异常。

这是我的 Controller :

public class SettingsPage implements PrizeReceiver{

    @FXML
    AnchorPane settingsAnchor;
    @FXML
    ListView<String> prizeList;
    @FXML
    TextField prizeField;
    @FXML
    Button load;

    void init(AnchorPane rootPane){

       BasicConfigurator.configure();

        AnchorPane root = null;

        try {

            File file = new File(System.getProperty("user.dir") + "/src/main/res/settings.fxml");

        root = FXMLLoader.load(file.toURI().toURL());
    } catch (IOException e) {
        e.printStackTrace();
    }

    if (root != null) {
        rootPane.getChildren().setAll(root);
    }

    initPrizeList();

}

private void initPrizeList() {

    GetPrizesThread getPrizesThread = new GetPrizesThread(Database.firebaseDatabase, this);
    getPrizesThread.getThread().start();

}

@FXML
void addPrize(){

    Database.createPrize(prizeField.getText());

}

@Override
public void receivePrizes(ArrayList<String> prizes) {

    ObservableList<String> items = FXCollections.observableArrayList();
    items.addAll(prizes);

    //Iv'e tried this if else with every fxml object and always prints null
    if(prizeField==null){
        System.out.println("NULL");
    }else {
        prizeList.getItems().addAll(items);
    }

}
}

这是主题:

public class GetPrizesThread implements Runnable {

private boolean finished = false;

private final Thread thread;
private FirebaseDatabase firebaseDatabase;
private PrizeReceiver prizeReceiver;

GetPrizesThread(FirebaseDatabase firebaseDatabase, PrizeReceiver prizeReceiver){
    this.firebaseDatabase = firebaseDatabase;
    this.prizeReceiver = prizeReceiver;
    thread = new Thread(this);
}

@Override
public void run() {

    ArrayList<String> prizes = new ArrayList<>();

    DatabaseReference databaseReference = firebaseDatabase.getReference("prizes");

    databaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot snapshot) {

            HashMap map = (HashMap) snapshot.getValue();

            Set keySet = map.keySet();

            ArrayList<String> listOfKeys = new ArrayList<String>(keySet);

            prizes.clear();
            prizes.addAll(listOfKeys);

            finished = true;

        }

        @Override
        public void onCancelled(DatabaseError error) {
            finished = true;
        }
    });

    while(!finished) {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    prizeReceiver.receivePrizes(prizes);

}

Thread getThread() {
    return thread;
}

}

这是 FXML 文件(使用场景构建器构建):

<AnchorPane fx:id="settingsAnchor" prefHeight="454.0" prefWidth="700.0" 
xmlns="http://javafx.com/javafx/8.0.161" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="SettingsPage">
   <children>
  <GridPane layoutX="-2.0" layoutY="-1.0" prefHeight="400.0" prefWidth="600.0">
    <columnConstraints>
      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
    </columnConstraints>
    <rowConstraints>
      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    </rowConstraints>
     <children>

        <ListView fx:id="prizeList" prefHeight="400.0" prefWidth="246.0" GridPane.rowSpan="3">
           <GridPane.margin>
              <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
           </GridPane.margin>
        </ListView>

        <Button mnemonicParsing="false" onAction="#addPrize" text="Add Prize" GridPane.columnIndex="1" GridPane.rowIndex="2">
           <GridPane.margin>
              <Insets left="8.0" top="40.0" />
           </GridPane.margin>
        </Button>
        <TextField fx:id="prizeField" promptText="Prize Name" GridPane.columnIndex="1" GridPane.rowIndex="2">
           <GridPane.margin>
              <Insets bottom="15.0" left="8.0" right="8.0" />
           </GridPane.margin>
        </TextField>
     </children>
  </GridPane>

当我没有使用 if/else 时,这是我的错误:

Exception in thread "Thread-5" java.lang.NullPointerException
at SettingsPage.receivePrizes(SettingsPage.java:74)
at GetPrizesThread.run(GetPrizesThread.java:59)
at java.lang.Thread.run(Thread.java:748)

第 74 行是

prizeList.getItems().addAll(items);

感谢您的帮助。

最佳答案

调用SettingsPage.init会导致

root = FXMLLoader.load(file.toURI().toURL());

创建 SettingsPage实例。如果 fxml 中存在 fx:controller 属性,FXMLLoader 会自动使用不带参数的构造函数来创建 Controller 实例。 fxml 中的对象被注入(inject)到这个新实例中。但是 GetPrizesThread 使用旧的 SettingsPage 实例。

您可以通过在加载 fxml 之前将 Controller 传递给 FXMLLoader 来解决此问题:

FXMLLoader loader = new FXMLLoader(file.toURI().toURL());
loader.setController(this);
root = loader.load();

您需要从 fxml 中删除 fx:controller 属性才能使其正常工作。

<小时/>

但是您的代码中存在不同的问题:

GUI 不得由 JavaFX 应用程序线程以外的线程访问,但 GetPrizesThread 在不同的线程上运行。您需要使用 Platform.runLater 进行 GUI 更新(或通过某种其他机制导致更新在应用程序线程上运行):

@Override
public void receivePrizes(ArrayList<String> prizes) {
    // do update on the javafx application thread
    Platform.runLater(() -> {
        prizeList.getItems().addAll(prizes);
    });
}

关于java - Controller 中的所有 JavaFX FXML 对象均为 null,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52884391/

相关文章:

java - 通过 for 循环将按钮添加到 FlowPane (JavaFX)

java - 由插件定义的 JAXB 编码/解码对象

java - 如何使用 Java 从属性 xml 中获取值?

JavaFX 完全定制的窗口?

menu - 适配 TableView 菜单按钮

java - 如何使用 id 获取 JavaFx 中的元素?

java - Java应用程序的用户注册设计

java - 将 CORBA 应用程序迁移到现代 Java 技术(Rest/SOAP/EJB)

java - 如何在 FXML (JavaFX) 中为按钮定义列

java - 关于使用 JavaFX 或 CSS 实现效果的效率