为什么仅为最后一个键按下生成KeyEvents?

时间:2015-10-06 15:52:44

标签: javafx-8

我正在测试关键处理程序,我遇到了一个问题。

最简单的形式,我有以下代码:

mainScene.setOnKeyPressed( event -> {
    System.out.println("Handler called for: " + event.getCode());
});

正如预期的那样,当按下某个键时,它会打印出相关的代码。

问题是,如果我一次按住2个键,只按下最后一个键会产生常量事件。我希望能够将按下的键添加到队列中以便在其他地方处理,但只有最后按下的键才会被添加到队列中。

有没有办法改变这种行为?

我能找到的唯一解决方法是使用地图来记录代码,并设置一个单独的按下并释放的处理程序来添加/删除地图中的代码。这可行,但需要不断轮询我可能需要响应的每个键,而不是只能检查按下的键队列是否为空。

1 个答案:

答案 0 :(得分:1)

我怀疑JVM正在接收来自操作系统的按键事件,因此当您按住两个键时,重复键行为将在操作系统级别确定。

要管理您自己的按键重复,您可以使用具有无限循环计数的时间轴;按下该键时启动时间线,并在释放该键时将其停止。您可能需要在Map<KeyCode, Timeline>中管理这些以处理多个密钥。让时间轴调用方法并传递密钥代码以便集中处理按键操作:这将避免轮询的需要。

SSCCE:

import java.util.HashMap;
import java.util.Map;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;


public class MultiRepeatKey extends Application {

    @Override
    public void start(Stage primaryStage) {

        Scene scene = new Scene(new Pane(), 400, 400);


        Map<KeyCode, Timeline> keyRepeats = new HashMap<>();
        Duration keyPressDelay = Duration.millis(200);

        scene.setOnKeyPressed(e -> {
            if (! keyRepeats.containsKey(e.getCode())) {
                Timeline repeat = new Timeline(new KeyFrame(Duration.ZERO, event -> processKey(e.getCode())),
                        new KeyFrame(keyPressDelay));
                repeat.setCycleCount(Animation.INDEFINITE);
                repeat.play();
                keyRepeats.put(e.getCode(), repeat);
            }
        });

        scene.setOnKeyReleased(e -> {
            if (keyRepeats.containsKey(e.getCode())) {
                Timeline repeat = keyRepeats.get(e.getCode());
                repeat.stop();
                keyRepeats.remove(e.getCode());
            }
        });

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void processKey(KeyCode code) {
        System.out.println(code.getName());
    }

    public static void main(String[] args) {
        launch(args);
    }
}

根据您的使用情况,可能对您有意义的另一个选项是只保留一个Map密钥以表示您想要的功能,然后保留Set这些功能的实现。然后使用AnimationTimer根据按下的键更新UI。 (AnimationTimer在每次帧渲染时执行其handle方法;传入的参数是以纳秒为单位的时间戳。)。

显然,如果你有很多映射,你可以在其他地方定义映射,但这就是想法:

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.DoubleFunction;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;


public class MultiRepeatKey extends Application {

    @Override
    public void start(Stage primaryStage) {


        Rectangle rect = new Rectangle(20, 20, 50, 50);
        rect.setFill(Color.CORNFLOWERBLUE);

        Pane pane = new Pane(rect);

        Set<DoubleFunction<Point2D>> motions = new HashSet<>();
        Map<KeyCode, DoubleFunction<Point2D>> keyMappings = new HashMap<>();
        keyMappings.put(KeyCode.UP, delta -> new Point2D(0, -delta));
        keyMappings.put(KeyCode.DOWN, delta -> new Point2D(0, delta));
        keyMappings.put(KeyCode.LEFT, delta -> new Point2D(-delta, 0));
        keyMappings.put(KeyCode.RIGHT, delta -> new Point2D(delta, 0));

        double speed = 150.0 ; // pixels / second

        AnimationTimer anim = new AnimationTimer() {

            private long lastUpdate = 0 ;

            @Override
            public void handle(long now) {

                if (lastUpdate > 0) {

                    double elapsedSeconds = (now - lastUpdate) / 1_000_000_000.0 ;
                    double delta = speed * elapsedSeconds ;

                    Point2D loc = motions.stream()
                            .map(m -> m.apply(delta))
                            .reduce(new Point2D(rect.getX(), rect.getY()), Point2D::add);
                    loc = clamp(loc, 0, 0, pane.getWidth() - rect.getWidth(), pane.getHeight() - rect.getHeight());        

                    rect.setX(loc.getX());
                    rect.setY(loc.getY());
                }

                lastUpdate = now ;
            }
        };
        anim.start();

        Scene scene = new Scene(pane, 400, 400);
        scene.setOnKeyPressed(e -> motions.add(keyMappings.get(e.getCode())));
        scene.setOnKeyReleased(e -> motions.remove(keyMappings.get(e.getCode())));


        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Point2D clamp(Point2D p, double minX, double minY, double maxX, double maxY) {
        if (p.getX() < minX) {
            p = new Point2D(minX, p.getY());
        } else if (p.getX() > maxX) {
            p = new Point2D(maxX, p.getY());
        }
        if (p.getY() < minY) {
            p = new Point2D(p.getX(), minY);
        } else if (p.getY() > maxY) {
            p = new Point2D(p.getX(), maxY);
        }
        return p ;
    }

    public static void main(String[] args) {
        launch(args);
    }
}