I have a problem of detecting a single key press in JavaFX. I have to detect arrow keys but each time I press any of those keys part of my code is being called multiple times. I realize that it is because AnimationTimer()
is a loop thus this is the reason but I have no idea how to detect single key tap. Anyways, here is the code:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.HashSet;
public class Main extends Application {
Stage window;
static Scene scene;
private static char[][] mapa = {
{'X','X','X','X','X'},
{'X','.','.','.','X'},
{'X','.','M','.','X'},
{'X','.','.','.','X'},
{'X','X','X','X','X'}
};
private final int sizeX = 16;
private final int sizeY = 16;
static HashSet<String> currentlyActiveKeys;
@Override
public void start(Stage primaryStage) throws Exception {
window = primaryStage;
window.setTitle("Hello World");
Group root = new Group();
scene = new Scene(root);
window.setScene(scene);
Canvas canvas = new Canvas(512 - 64, 256);
root.getChildren().add(canvas);
prepareActionHandlers();
GraphicsContext gc = canvas.getGraphicsContext2D();
new AnimationTimer() {
@Override
public void handle(long now) {
gc.clearRect(0,0,512,512);
for(int i = 0; i < mapa.length; i++) {
for(int j = 0; j < mapa[i].length; j++) {
if(mapa[i][j] == 'X') {
gc.setLineWidth(5);
gc.setFill(Color.RED);
gc.fillRect(sizeX + i*sizeX, sizeY + j*sizeY, sizeX, sizeY);
} else if(mapa[i][j] == '.') {
gc.setLineWidth(5);
gc.setFill(Color.GREEN);
gc.fillRect(sizeX + i*sizeX, sizeY + j*sizeY, sizeX, sizeY);
} else if(mapa[i][j] == 'M') {
gc.setLineWidth(5);
gc.setFill(Color.BLACK);
gc.fillRect(sizeX + i*sizeX, sizeY + j*sizeY, sizeX, sizeY);
}
}
}
if(currentlyActiveKeys.contains("LEFT")) {
System.out.println("left");
}
if(currentlyActiveKeys.contains("RIGHT")) {
System.out.println("right");
}
if(currentlyActiveKeys.contains("UP")) {
System.out.println("up");
}
if(currentlyActiveKeys.contains("DOWN")) {
System.out.println("down");
}
}
}.start();
window.show();
}
private static void prepareActionHandlers()
{
currentlyActiveKeys = new HashSet<String>();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event)
{
currentlyActiveKeys.add(event.getCode().toString());
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event)
{
currentlyActiveKeys.remove(event.getCode().toString());
}
});
}
public static void main(String[] args) {
launch(args);
}
}
When I press down arrow button (same goes with other keys of course), in the scope I am getting results like:
down
down
down
down
down
down
Obviously this happens as long as I press the button. Once I stop pressing it, printing ends. What I would love to achieve is that once I press a button (no matter how long I hold it) I will get down
just once. I need this because I would like to update the color of my rectangles in the canvas
.
By adding both key press and key release handlers and a boolean state for each key, you can keep track of whether you have processed the key since it was pressed. You can then reset that processed state whenever the key is released so that the next time it is really pressed you can handle it.
Use KeyEvent. getKeyChar() and KeyEvent. getKeyCode() to find out which key the user pressed.
getCode() The key code associated with the key in this key pressed or key released event. EventType<KeyEvent> getEventType()
Showing a Stage The difference between the JavaFX Stage methods show() and showAndWait() is, that show() makes the Stage visible and the exits the show() method immediately, whereas the showAndWait() shows the Stage object and then blocks (stays inside the showAndWait() method) until the Stage is closed.
What is happening is that the OS has a kind of automatic typing function, such that when you hold a key down, it keeps generating key press events, even though you aren't really pressing the key more than once.
By adding both key press and key release handlers and a boolean state for each key, you can keep track of whether you have processed the key since it was pressed. You can then reset that processed state whenever the key is released so that the next time it is really pressed you can handle it.
Sample Application
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.util.HashMap;
public class Main extends Application {
private HashMap<String, Boolean> currentlyActiveKeys = new HashMap<>();
@Override
public void start(Stage stage) throws Exception {
final Scene scene = new Scene(new Group(), 100, 100);
stage.setScene(scene);
scene.setOnKeyPressed(event -> {
String codeString = event.getCode().toString();
if (!currentlyActiveKeys.containsKey(codeString)) {
currentlyActiveKeys.put(codeString, true);
}
});
scene.setOnKeyReleased(event ->
currentlyActiveKeys.remove(event.getCode().toString())
);
new AnimationTimer() {
@Override
public void handle(long now) {
if (removeActiveKey("LEFT")) {
System.out.println("left");
}
if (removeActiveKey("RIGHT")) {
System.out.println("right");
}
if (removeActiveKey("UP")) {
System.out.println("up");
}
if (removeActiveKey("DOWN")) {
System.out.println("down");
}
}
}.start();
stage.show();
}
private boolean removeActiveKey(String codeString) {
Boolean isActive = currentlyActiveKeys.get(codeString);
if (isActive != null && isActive) {
currentlyActiveKeys.put(codeString, false);
return true;
} else {
return false;
}
}
public static void main(String[] args) {
launch(args);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With