I'm trying to detect when a button's action is triggered (which could be through clicking or something else like the keyboard) while the "shortcut" key is pressed. I couldn't find a way to get the keys pressed from an ActionEvent, so I put event filters for key press and release on the scene which keep track of which keys are pressed. Then in the button's action I check if the shortcut key is pressed.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.HashSet;
import java.util.Set;
public class App extends Application {
private final Set<KeyCode> pressedKeys = new HashSet<>();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Button button = new Button("Click with shortcut key pressed");
button.setOnAction(e -> {
if (pressedKeys.contains(KeyCode.SHORTCUT)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
});
VBox vBox = new VBox(button);
vBox.setStyle("-fx-background-color:red;");
Scene scene = new Scene(vBox);
scene.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
System.out.println("Key pressed: " + e.getCode());
pressedKeys.add(e.getCode());
});
scene.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
System.out.println("Key released: " + e.getCode());
pressedKeys.remove(e.getCode());
});
stage.setScene(scene);
stage.show();
}
public static class AppRunner {
public static void main(String[] args) {
App.main(args);
}
}
}
However I find there are 2 problems:
- When I'm holding down the shortcut key and clicking on the button, the action isn't fired at all (neither success or failure are printed)
- If I change the button's action to a click event filter I always get failure. Using IntelliJ's debugger I see the
KeyCode.CONTROLkey is known to be pressed, but not theSHORTCUTkey, even though they are the same key in this case. How do I check if any of the pressed keys are the shortcut key in a platform independent way?
button.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {
if (pressedKeys.contains(KeyCode.SHORTCUT)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
});
I also need this to work with actions on other controls, such as ComboBoxes and TextFields.
Edit: I found another bug with this approach. If the user holds down a key and switches to another scene or application then releases the key, the scene won't detect the key release and will still think the key is pressed. So I could do with a way to detect what keys are down without tracking every key press/release. How are MouseEvents informed of which keys are down so they can use MouseEvent.isShortcutDown()? Can the same method be used here?
Edit: To answer jewelsea's question for more background, it's a little complicated but imagine I have a form with multiple types of controls that can be used to set values in the form, and the form needs to be filled out many times. Therefore I want a way not only to set a value in the form but also fix it or make it the default so that for the next time it needs filling out it will be pre-populated with that value. The shortcut key is a convenient way to differentiate between setting the value without fixing it and setting it while fixing it. This can apply to shortcut-clicking a button or dropdown item from a combobox or holding the shortcut key while pressing enter in a text field. I also think answers to this question will be more useful to others if they work for action events on any control, but even getting it to work for a button would be good.

I managed to solve all 3 problems without re-implementing the action triggering logic. The solution can be applied to any new control just by calling
makeShortcutActionable(control). It's platform-independent and works well for other modifier keys and non-modifier keys. However, the solution is somewhat hacky and for controls that use the same modifier key already (e.g., Ctrl-A/Cmd-A to select all text in a TextField or editable ComboBox) it disables the original use of the modifier key.Solving problem 1: The way we get the action to fire when the shortcut (or any modifier key) is pressed is to fool the control into thinking it isn't pressed. For that we consume the event if
event.isShortcutDown()is true and then fire a copy of the event which thinks the shortcut key isn't pressed. The way the action event handler knows the shortcut key is pressed is by asking the scene which keeps track of which keys are pressed. Unfortunately the default event handlers of the control don't know the shortcut key is pressed so Ctrl-A/Cmd-A in aTextFieldwon't do anything (but luckily it doesn't type an A either). Therefore for TextFields it's probably better to add an event handler that looks for shortcut-enter instead (re-implementing the action logic is easy forTextField).Solving problem 2: The scene not only records the
KeyCodesthat were pressed/released but also checksevent.isShortcutDown(). When modifying events for re-firing we have to know which modifier key is the shortcut key so we useToolkit.getToolkit().getPlatformShortcutKey(), which is whatisShortcutDown()uses in the source code.Solving problem 3: Tracking key states through key presses and releases is insufficient when switching between windows so we use
isShortcutDown()instead. The scene tracks all key and mouse events so basically once there is any interaction with the scene again it checks if the shortcut key is pressed (which happens before any action event can be triggered). Non-modifier keys can't be checked in the same way, but when they are held down they continue to fire KeyEvents. Therefore when the scene loses focus we can assume all non-modifier keys were released and when the scene gets focus again we will quickly be informed which keys are still pressed. Finally because we are firing fake events which claim the shortcut key isn't pressed we have to tell the key tracker which events are fake so it can ignore them.Here's an example with a
Button, aComboBoxand aTextField. Each has an action which checks whether the shortcut or S key is pressed. For theTextFieldI opted to use an event handler for the shortcut-enter combo instead of breaking the shortcut-based keyboard commands.