Thursday, May 28, 2015

Handling Keyboard Events in JavaFX

When using JavaFX, SceneBuilder is not terribly helpful when setting up keyboard events.  This is because SceneBuilder's event handlers can't take any parameters.  Consequently, there is no way to determine which key triggered the event.

I've made an amusing little program that has four triangles on the GUI, one for each arrow key.  When an arrow key is pressed, the corresponding triangle changes color.  When it is released, it switches back to the original color.  Here is the source code for its controller; constructing the rest of the GUI is an exercise for the reader.

package application;

import java.util.Map;
import java.util.TreeMap;

import javafx.fxml.FXML;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;

public class ButtonPaneController {
 @FXML
 Polygon up;
 @FXML
 Polygon down;
 @FXML
 Polygon left;
 @FXML
 Polygon right;
 @FXML
 AnchorPane pane;
 
 Color pressColor = Color.GOLD;
 Color releaseColor = Color.DODGERBLUE;
 
 Map<KeyCode,Polygon> key2poly;
 
 @FXML
 void initialize() {
  pane.setOnKeyPressed(key -> recolor(key.getCode(), 
    pressColor));
  pane.setOnKeyReleased(key -> recolor(key.getCode(), 
    releaseColor));
  pane.setOnMouseEntered(m -> pane.requestFocus());
  key2poly = new TreeMap<>();
  key2poly.put(KeyCode.LEFT, left);
  key2poly.put(KeyCode.RIGHT, right);
  key2poly.put(KeyCode.UP, up);
  key2poly.put(KeyCode.DOWN, down);
 }
 
 void recolor(KeyCode code, Color newColor) {
  if (key2poly.containsKey(code)) {
   key2poly.get(code).setFill(newColor);
  }
  pane.requestFocus();
 }
}

In the initialize() method, we set up our event handlers using lambdas. Whenever a key is pressed, recolor() is called with the key code and the target color upon a press.  Similarly, whenever a key is released, the same method call is made, but with a different target color.  Finally, we request the focus whenever the mouse enters the pane. This could easily have been set up in SceneBuilder, but I did it in code here for consistency.

Having set up the handlers, I use a Map to simplify the implementation.  By mapping each key to the corresponding GUI element that is to have its color changed, I avoid a bothersome set of if-else clauses in the recolor() method.  A key-based event handler with more substantive algorithmic content could use a Map in a similar way where the values are lambda expressions.

No comments:

Post a Comment