JavaFX中fx:id运行后消失且Rectangle空指针异常求助
JavaFX Rectangle空指针异常及fx:id消失问题解决
问题概述
编写JavaFX代码实现按键增加名为stick的Rectangle高度功能,此前按键触发文本输出正常,但为Rectangle关联fx:id后运行抛出空指针异常,且每次运行后SceneBuilder中的fx:id会消失。
错误信息
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException: Cannot invoke "javafx.scene.shape.Rectangle.getHeight()" because "this.this$0.stick" is null at com.example.demo2@1.0-SNAPSHOT/com.example.demo2.SceneController$1.handle(SceneController.java:105) at com.example.demo2@1.0-SNAPSHOT/com.example.demo2.SceneController$1.handle(SceneController.java:83) at javafx.base@21-ea/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86) at javafx.base@21-ea/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:232) at javafx.base@21-ea/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:189) at javafx.base@21-ea/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59) at javafx.base@21-ea/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58) at javafx.base@21-ea/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) at javafx.base@21-ea/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56) at javafx.base@21-ea/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) at javafx.base@21-ea/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74) at javafx.base@21-ea/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54) at javafx.base@21-ea/javafx.event.Event.fireEvent(Event.java:198) at javafx.graphics@21-ea/javafx.scene.Scene.processKeyEvent(Scene.java:2194) at javafx.graphics@21-ea/javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2715) at javafx.graphics@21-ea/com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:218) at javafx.graphics@21-ea/com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:150) at java.base/java.security.AccessController.doPrivileged(AccessController.java:400) at javafx.graphics@21-ea/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$1(GlassViewEventHandler.java:250) at javafx.graphics@21-ea/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424) at javafx.graphics@21-ea/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:249) at javafx.graphics@21-ea/com.sun.glass.ui.View.handleKeyEvent(View.java:542) at javafx.graphics@21-ea/com.sun.glass.ui.View.notifyKey(View.java:966) at javafx.graphics@21-ea/com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at javafx.graphics@21-ea/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:185) at java.base/java.lang.Thread.run(Thread.java:1583)
问题控制器代码
package com.example.demo2; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import java.io.IOException; import java.util.Objects; public class SceneController { @FXML private Rectangle stick; @FXML private Stage stage; @FXML private boolean gameIsRunning = false; @FXML private javafx.scene.control.Label pausedLabel = new Label(); @FXML private Scene scene; @FXML private Scene gameScene; @FXML private Scene pausedScene; @FXML private Parent root; @FXML private javafx.scene.control.Button closeButton; @FXML public void switchToMainMenu(ActionEvent event) throws IOException { root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("hello-view.fxml"))); stage = (Stage)((Node)event.getSource()).getScene().getWindow(); scene = new Scene(root); stage.setScene(scene); stage.show(); } @FXML public void switchToMainMenuFromGame() throws IOException { root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("hello-view.fxml"))); scene = new Scene(root); stage.setScene(scene); stage.show(); } @FXML private void createBridge(KeyEvent event){ switch (event.getCode()){ case C -> stick.setHeight(stick.getHeight()+10.0); } } @FXML public void switchToGame(ActionEvent event) throws IOException { gameIsRunning = true; Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("game-view.fxml"))); stage = (Stage)((Node)event.getSource()).getScene().getWindow(); scene = new Scene(root); gameScene = scene; scene.setOnKeyPressed(new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent keyEvent) { switch (keyEvent.getCode()){ case ESCAPE -> { System.out.println("paused game"); pauseGame(); try { switchToPausedScene(); } catch (IOException e) { throw new RuntimeException(e); } } case Q -> { try { switchToMainMenuFromGame(); } catch (IOException e) { throw new RuntimeException(e); } } case C -> { System.out.println("creating bridge"); stick.setHeight(stick.getHeight()+10.0); } case X -> System.out.println("switching sides"); } } }); stage.setScene(scene); stage.show(); } @FXML private void switchToPausedScene() throws IOException{ gameIsRunning = false; if(null == pausedScene){ root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("paused-menu.fxml"))); scene = new Scene(root); pausedScene = scene; }else{ scene = pausedScene; } scene.setOnKeyPressed(new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent keyEvent) { if (Objects.requireNonNull(keyEvent.getCode()) == KeyCode.ESCAPE) { try { switchToGameSceneFromPaused(); } catch (IOException e) { throw new RuntimeException(e); } } } }); stage.setScene(scene); stage.show(); } @FXML private void switchToGameSceneFromPaused() throws IOException{ scene = gameScene; stage.setScene(scene); stage.show(); gameIsRunning = true; } @FXML protected void onExitButtonClick() { Stage stage = (Stage) closeButton.getScene().getWindow(); stage.close(); } @FXML private void pauseGame(){ gameIsRunning = false; } @FXML private void resumeGame(){ gameIsRunning = true; } }
game-view.fxml代码
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.shape.Rectangle?> <AnchorPane prefHeight="966.0" prefWidth="540.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.demo2.SceneController"> <children> <Rectangle fx:id="stick" arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="6.0" layoutX="287.0" layoutY="225.0" stroke="BLACK" strokeType="INSIDE" width="6.0" /> </children> </AnchorPane>
解决方案
1. 空指针异常修复
空指针的核心原因:switchToGame方法中使用FXMLLoader.load()静态方法加载game-view.fxml时,会生成一个全新的SceneController实例,而当前控制器实例的stick字段并未被这个加载过程注入(当前实例来自之前的FXML文件)。你在匿名事件处理器中访问的是当前实例的stick,自然为null。
修改switchToGame方法,获取game-view.fxml对应的控制器实例:
@FXML public void switchToGame(ActionEvent event) throws IOException { gameIsRunning = true; // 改用非静态加载方式,获取控制器实例 FXMLLoader loader = new FXMLLoader(getClass().getResource("game-view.fxml")); Parent root = loader.load(); SceneController gameController = loader.getController(); stage = (Stage)((Node)event.getSource()).getScene().getWindow(); scene = new Scene(root); gameScene = scene; scene.setOnKeyPressed(keyEvent -> { switch (keyEvent.getCode()){ case ESCAPE -> { System.out.println("暂停游戏"); pauseGame(); try { switchToPausedScene(); } catch (IOException e) { throw new RuntimeException(e); } } case Q -> { try { switchToMainMenuFromGame(); } catch (IOException e) { throw new RuntimeException(e); } } case C -> { System.out.println("创建桥梁"); // 使用gameController的stick字段操作 if(gameController.stick != null){ gameController.stick.setHeight(gameController.stick.getHeight()+10.0); } } case X -> System.out.println("切换 sides"); } }); stage.setScene(scene); stage.show(); }
2. fx:id消失问题修复
- 移除控制器中所有
@FXML字段的手动初始化,比如private Label pausedLabel = new Label();改为private Label pausedLabel;,手动初始化会覆盖FXML注入的值,也会导致SceneBuilder识别异常。 - 只给需要FXML注入的字段添加
@FXML注解,stage、scene、gameIsRunning等手动赋值的字段不需要加@FXML。 - 确保
game-view.fxml中fx:controller属性正确指向com.example.demo2.SceneController,保存FXML后重新打开SceneBuilder,fx:id就不会消失了。
内容的提问来源于stack exchange,提问作者Surat
相关产品推荐
相关产品推荐

