由于 Button 类的原因,无法处理来自键盘的事件。 JavaFX(已更改)

问题描述 投票:0回答:1

我正在使用 javafx 编写一个原始的 java 游戏。 我通过按下按钮来执行暂停功能

@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;
        });

我测试发现按键盘上的任意键都会被程序读取为按按钮。也就是说,导致问题的是按钮的存在,而不是处理该按钮的方法。 如何同时处理键盘事件和按钮点击?

这是我的代码。


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();
    }
}

控制器


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.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>

javafx
1个回答
0
投票

解决方案

将正在消耗事件的按钮的焦点遍历设置为 false。

<Button fx:id="btnPause" focusTraversable="false"/>

解释

了解事件处理如何工作

有一个捕获阶段,然后是冒泡阶段。

如果某些东西在处理过程中消耗了事件,它不会冒泡回场景进行处理。

这就是您向场景添加按钮时发生的情况。

按钮可以获取焦点并接收输入。当你只有一个按钮时,它就有焦点,因为没有其他东西可以关注。

一旦按钮获得焦点,它就会通过硬连线接受来自键盘的输入。如果它接受输入,它将消耗它,并且输入不会冒泡回场景中的事件处理程序。

按钮将处理某些关键事件。您可以在

com.sun.javafx.scene.control.behavior.ButtonBehavior
类的代码中看到:

// create a map for button-specific mappings (this reuses the default
// InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings)
buttonInputMap = createInputMap();

// add focus traversal mappings
addDefaultMapping(buttonInputMap, FocusTraversalInputMap.getFocusTraversalMappings());

// then button-specific mappings for key and mouse input
addDefaultMapping(buttonInputMap,
    new KeyMapping(SPACE, KeyEvent.KEY_PRESSED, this::keyPressed),
    new KeyMapping(SPACE, KeyEvent.KEY_RELEASED, this::keyReleased),
    new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed),
    new MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased),
    new MouseMapping(MouseEvent.MOUSE_ENTERED, this::mouseEntered),
    new MouseMapping(MouseEvent.MOUSE_EXITED, this::mouseExited),

    // on non-Mac OS platforms, we support pressing the ENTER key to activate the button
    new KeyMapping(new KeyBinding(ENTER, KeyEvent.KEY_PRESSED), this::keyPressed, event -> PlatformUtil.isMac()),
    new KeyMapping(new KeyBinding(ENTER, KeyEvent.KEY_RELEASED), this::keyReleased, event -> PlatformUtil.isMac())
);

// Button also cares about focus
control.focusedProperty().addListener(focusListener);

如果按钮具有焦点,则输入映射中的任何键都将被按钮消耗。

焦点遍历键被添加到输入映射中(这些是上、下、左、右箭头键),因此焦点遍历机制会消耗这些键,并且场景中的左右箭头键处理程序不起作用(您拥有的导航备用键(D 和 A)确实有效,因为它们没有被消耗)。

应用程序中的 Enter 键处理程序适用于 Mac,但不适用于 Windows。 Windows 对按钮的 Enter 键按下进行了特殊处理,这将消耗该平台上的 Enter 键。

一般输入处理方法

以下是一些替代修复方法:

  1. 不要让按钮获得焦点,除非您的应用程序处于按钮消耗事件(例如游戏暂停)的状态。
    • 上面建议的关闭暂停按钮焦点遍历的解决方案就采用了这种策略。
  2. 在场景中使用事件过滤器而不是事件处理程序,并在按钮使用事件之前拦截事件。
    • 使用过滤器的一个副作用是,您的应用程序和按钮都可能会执行该事件(如果您不在过滤器中使用它)。这可能可以,也可能不行,取决于你想要什么。
    • 或者,如果确实消耗了过滤器中的事件,则按钮无法对其进行操作,这又可能是好的,也可能不是。
要解决焦点遍历问题,请关闭暂停按钮的焦点遍历。在按钮的 FXML 中写入:

focusTraversable="false"
您已经在 FXML 中为标签设置了该设置(这是多余的,因为默认情况下标签不可焦点遍历),但您没有为按钮设置它。

一旦将按钮的焦点遍历设置为关闭,游戏的左右箭头导航键处理程序就开始工作。

在按钮上将焦点遍历设置为 false 还可以修复 Windows 上的 Enter 键问题,因为如果按钮没有焦点,则不会消耗 Enter 键,并且无法保留焦点,因为它不是焦点的一部分遍历图。

© www.soinside.com 2019 - 2024. All rights reserved.