JavaFX中同时处理键盘事件与Button点击的问题求助
我用JavaFX写了个简易游戏,通过Button实现暂停功能:
@FXML private Button btnPause;
暂停状态由私有字段private boolean isPause控制,点击方法切换状态:
@FXML private void pauseClick(ActionEvent event){ isPause = !isPause; }
我在HelloApplication类里处理键盘事件:
scene.setOnKeyPressed(e -> { if (e.getCode() == KeyCode.ENTER) HelloController.jump = true; if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D) HelloController.right = true; if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A) HelloController.left = true; }); scene.setOnKeyReleased(e -> { if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D) HelloController.right = false; if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A) HelloController.left = false; });
但只要添加了这个Button,所有键盘按键都被识别为按钮点击,无法正常触发键盘事件。请问怎么同时处理Button点击和键盘事件?
相关代码
HelloApplication代码
import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.input.KeyCode; import javafx.stage.Stage; import javafx.stage.StageStyle; import java.io.IOException; public class HelloApplication extends Application { @Override public void start(Stage stage) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml")); Scene scene = new Scene(fxmlLoader.load(), 711, 400); stage.initStyle(StageStyle.UNDECORATED); stage.setScene(scene); scene.setOnKeyPressed(e -> { if (e.getCode() == KeyCode.ENTER) HelloController.jump = true; if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D) HelloController.right = true; if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A) HelloController.left = true; }); scene.setOnKeyReleased(e -> { if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D) HelloController.right = false; if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A) HelloController.left = false; }); stage.show(); } public static void main(String[] args) { launch(); } }
Controller代码
import java.net.URL; import java.util.ResourceBundle; import javafx.animation.*; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.input.ActionEvent; import javafx.scene.shape.Rectangle; import javafx.util.Duration; public class HelloController { @FXML private ResourceBundle resources; @FXML private URL location; @FXML private Rectangle bg1, bg2, player, enemy; @FXML private Button btnPause; @FXML private Label labelPause; private final int BG_WEIGHT = 711; private ParallelTransition parallelTransition; private TranslateTransition enemyTrans; public static boolean right = false, left = false, jump = false; private boolean isPause = false; private int playerSpeed = 3, jumpSpeed = 4; AnimationTimer timer = new AnimationTimer() { @Override public void handle(long l) { if (jump && player.getLayoutY() > 60f) player.setLayoutY(player.getLayoutY() - jumpSpeed); else if (player.getLayoutY() <= 185f){ jump = false; player.setLayoutY(player.getLayoutY() + jumpSpeed); } if (right && player.getLayoutX() < 100) player.setLayoutX(player.getLayoutX() + playerSpeed); if (left && player.getLayoutX() > 0) player.setLayoutX(player.getLayoutX() - playerSpeed); if (isPause && !labelPause.isVisible()) { labelPause.setVisible(true); playerSpeed = 0; jumpSpeed = 0; parallelTransition.pause(); enemyTrans.pause(); } else if (!isPause && labelPause.isVisible()) { labelPause.setVisible(false); playerSpeed = 3; jumpSpeed = 4; parallelTransition.play(); enemyTrans.play(); } } }; @FXML private void pauseClick(ActionEvent event){ isPause = !isPause; } @FXML void initialize() { TranslateTransition bgOneTrans = new TranslateTransition(Duration.millis(5000), bg1); bgOneTrans.setFromX(0); bgOneTrans.setToX(BG_WEIGHT * -1); bgOneTrans.setInterpolator(Interpolator.LINEAR); TranslateTransition bgTwoTrans = new TranslateTransition(Duration.millis(5000), bg2); bgTwoTrans.setFromX(0); bgTwoTrans.setToX(BG_WEIGHT * -1); bgTwoTrans.setInterpolator(Interpolator.LINEAR); enemyTrans = new TranslateTransition(Duration.millis(3500), enemy); enemyTrans.setFromX(0); enemyTrans.setToX(BG_WEIGHT * -1 - 200); enemyTrans.setInterpolator(Interpolator.LINEAR); enemyTrans.setCycleCount(Animation.INDEFINITE); enemyTrans.play(); parallelTransition = new ParallelTransition(bgOneTrans, bgTwoTrans); parallelTransition.setCycleCount(Animation.INDEFINITE); parallelTransition.play(); timer.start(); } }
FXML文件代码
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.shape.Rectangle?> <?import javafx.scene.text.Font?> <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="711.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.realgame.HelloController"> <children> <Rectangle fx:id="bg1" arcHeight="5.0" arcWidth="5.0" fill="#e2ff1f" height="400.0" stroke="#ffffff00" strokeType="INSIDE" width="711.0" /> <Rectangle fx:id="bg2" arcHeight="5.0" arcWidth="5.0" fill="#26ff00" height="400.0" layoutX="709.0" stroke="#ffffff00" strokeType="INSIDE" width="711.0" /> <Rectangle fx:id="player" arcHeight="5.0" arcWidth="5.0" fill="#13b734" height="124.0" layoutX="42.0" layoutY="225.0" stroke="BLACK" strokeType="INSIDE" width="124.0" /> <Rectangle fx:id="enemy" arcHeight="5.0" arcWidth="5.0" fill="#c51212" height="87.0" layoutX="853.0" layoutY="264.0" stroke="BLACK" strokeType="INSIDE" width="87.0" /> <Label fx:id="labelPause" focusTraversable="false" layoutX="239.0" layoutY="109.0" text="PAUSE" textFill="#4d0aff" visible="false"> <font> <Font name="Bell MT Bold" size="84.0" /> </font> </Label> <Button fx:id="btnPause" layoutX="23.0" layoutY="18.0" mnemonicParsing="false" text="Pause" /> </children> </AnchorPane>
解决方案
问题根源是Button默认会自动获取焦点,导致键盘事件优先被Button消费(比如空格、回车会触发按钮点击),无法传递到Scene。以下是两种有效解决方法:
方法1:设置Button的focusTraversable为false
在FXML中给Button添加focusTraversable="false"属性,这样Button不会自动获取焦点,焦点会保留在根容器(AnchorPane)上,键盘事件就能正常被Scene捕获:
<Button fx:id="btnPause" layoutX="23.0" layoutY="18.0" mnemonicParsing="false" text="Pause" focusTraversable="false" />
方法2:让根容器主动获取焦点
- 给FXML中的AnchorPane添加
fx:id="rootPane",然后在Controller的initialize方法中请求焦点:
@FXML private AnchorPane rootPane; @FXML void initialize() { // 其他初始化代码... rootPane.setFocusTraversable(true); rootPane.requestFocus(); timer.start(); }
这样即使Button偶尔获取焦点,程序启动时根容器会重新拿到焦点,确保键盘事件正常触发。
额外优化:避免静态变量传递状态
代码中用HelloController.right等静态变量传递键盘状态,这不是JavaFX的最佳实践。建议将键盘事件处理移到Controller中,直接操作实例变量,比如:
// 在Controller中添加键盘事件处理 @FXML void initialize() { // 其他代码... rootPane.setOnKeyPressed(e -> { if (e.getCode() == KeyCode.ENTER) jump = true; if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D) right = true; if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A) left = true; }); rootPane.setOnKeyReleased(e -> { if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D) right = false; if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A) left = false; }); rootPane.setFocusTraversable(true); rootPane.requestFocus(); timer.start(); }
然后把right、left、jump改成非静态变量:
private boolean right = false, left = false, jump = false;
内容的提问来源于stack exchange,提问作者Антон Ледовский
相关产品推荐
相关产品推荐

