-1

I am making a Tetris clone with JavaFX, and what I am "trying to do is allow the user to configure custom key bindings for specific actions." I am not using a FXML file or controllers; I am using Java JDK 8.

I am making a "Set Custom Controls" menu, using Stage and Scene. I want to list all the different controls in Labels with a Button next to each.

When the user clicks a Button, I want that to activate an EventListener<KeyEvent> to grab just the first key pressed after clicking that Button in order to assign that KeyCode to the corresponding `KeyCode class variable.

Also, if the user decides they made a mistake clicking the Button, I want to allow them to click the Button again to cancel the EventListener<KeyEvent>.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
Khardrix
  • 25
  • 6
  • So what is the question? – Queeg Jul 25 '22 at 20:20
  • How to make clicking inside a TextBox activate a KeyListener to grab just the next key pressed. – Khardrix Jul 25 '22 at 20:30
  • 1
    Do you mean TextField? – Giovanni Contreras Jul 25 '22 at 21:00
  • 3
    This just sounds like *completely* the wrong approach to whatever it is you’re trying to do. You should never use a key listener with a text input control. – James_D Jul 25 '22 at 21:21
  • I should use a Button instead of a TextField, but what I'm trying to accomplish is what I have seen in nearly every PC game I've ever played. In the controls menu, it lists the different controls with the current key assigned to each control. When you click that control's Button, it takes whatever key you press next and assigns it to that control. A KeyListener is the only thing I can think of, but if you have a different approach, I would be glad to hear it. – Khardrix Jul 25 '22 at 22:01
  • 1
    It sounds like what you're trying to do is allow the user to configure custom key bindings for specific actions. Is that correct? – Slaw Jul 25 '22 at 22:02
  • @Slaw that is correct – Khardrix Jul 25 '22 at 22:03
  • Maybe you want an [accelerator](https://stackoverflow.com/questions/12710468/using-javafx-2-2-mnemonic-and-accelerators/12714730#12714730), but that is just for delegating the key presses to controls, not necessary for dynamically changing the keys which will access a control. I don't have a suggestion for you for recording key combinations to assign to accelerators. – jewelsea Jul 26 '22 at 00:41
  • 1
    I was able to figure it out. I added EventHandler handlers in start(). When a user clicks the Button for the corresponding control, that event handler is added from inside start(). When the user hits a key, it assigns the key and then removes the EventHandler. Thank you for the help. – Khardrix Jul 26 '22 at 07:22
  • You can answer your own question. Well, at least you could if it wasn’t closed, perhaps next time :-) – jewelsea Jul 26 '22 at 08:50
  • @jewelsea: I'd like to offer a different approach; voting to reopen. – trashgod Jul 26 '22 at 11:13
  • I think it needs one or two more votes to be reopened. – jewelsea Jul 26 '22 at 19:22
  • I edited the title and text. Hoping it will be reopened upon review. Thank you for the help. – Khardrix Jul 26 '22 at 19:25

2 Answers2

2

As suggested in a comment by @Slaw, you want to "allow the user to configure custom key bindings for specific actions." One approach would be "a key-event editor for JavaFX like the one cited here for Swing."

The example below focuses narrowly on capturing raw key presses. As suggested here, a KeyEvent listener captures the user's keystroke, displaying the name of the game event and updating the pressed key code's name. KeyCode.TAB is reserved for navigation, and the rest are consumed. The resulting key event and game event are stored in a Map for later reference. As @James_D comments, typical usage would "never use a key listener with a text input control." In this case, the focused, but non-editable TextField conveniently displays the pressed key's name.

image

import java.util.HashMap;
import java.util.Map;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/** @see https://stackoverflow.com/a/73130018/230513 */
public class KeyCodeSelector extends Application {

    private final Map<KeyEvent, String> map = new HashMap<>();

    @Override
    public void start(Stage stage) {
        var root = new VBox();
        root.getChildren().add(createKeyPane("Left: ", KeyCode.LEFT));
        root.getChildren().add(createKeyPane("Right: ", KeyCode.RIGHT));
        var scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    private Pane createKeyPane(String s, KeyCode k) {
        var label = new Label(s);
        label.setAlignment(Pos.BASELINE_RIGHT);
        label.setMinWidth(50);
        var textField = new TextField(k.getName());
        label.setLabelFor(textField);
        textField.setEditable(false);
        textField.setOnKeyPressed((keyEvent) -> {
            var keyCode = keyEvent.getCode();
            if (keyCode != KeyCode.TAB) {
                textField.setText(keyCode.getName());
                map.put(keyEvent, s);
                keyEvent.consume();
            }
        });
        var grid = new GridPane();
        grid.add(label, 0, 0);
        grid.add(textField, 0, 1);
        return new HBox(label, textField);
    }

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

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 1
    This looks great. I was having trouble figuring out how to word my question, because I didn't really know how to implement what I wanted. Thank you for your time and help. – Khardrix Jul 26 '22 at 21:44
1

I will share my solution for anyone who runs into problem. I will use my implementation for setting the key binding for moving a block left.

I created the class variables:

private Scene sceneControlOptions;
private EventHandler<KeyEvent> keyHandlerBtnMoveLeft;
private Button btnMoveLeft;
private KeyCode controlButtonMoveLeft;

Inside the method to create and display my control options menu (Stage and Scene):

btnMoveLeft.setOnMouseClicked(e -> {
            btnMoveLeft.setText("---");
            sceneControlOptions.addEventHandler(KeyEvent.KEY_PRESSED, keyHandlerBtnMoveLeft);
        });

And finally, inside of start() I added:

keyHandlerBtnMoveLeft = new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                KeyCode key = event.getCode();
                setControlButtonMoveLeft(key);
                btnMoveLeft.setText(key.getName().toString());
                sceneControlOptions.removeEventHandler(KeyEvent.KEY_PRESSED, keyHandlerBtnMoveLeft);
            }
        };

Where setControlButtonMoveLeft(key) is:

private void setControlButtonMoveLeft(KeyCode key) {
    this.controlButtonMoveLeft = key;
}
Khardrix
  • 25
  • 6