Using JavaFX 2.2 Mnemonic (and accelerators)

For your use case, I think you actually want to use an accelerator rather than a mnemonic.

button.getScene().getAccelerators().put(
  new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN), 
  new Runnable() {
    @Override public void run() {
      button.fire();
    }
  }
);

In most cases it is recommended that you use KeyCombination.SHORTCUT_DOWN as the modifier specifier, as in the code above. A good explanation of this is in the KeyCombination documentation:

The shortcut modifier is used to represent the modifier key which is
used commonly in keyboard shortcuts on the host platform. This is for
example control on Windows and meta (command key) on Mac. By using
shortcut key modifier developers can create platform independent
shortcuts. So the “Shortcut+C” key combination is handled internally
as “Ctrl+C” on Windows and “Meta+C” on Mac.

If you wanted to specifically code to only handle a Ctrl+S key combination, they you could use:

new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)

Here is an executable example:

import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class SaveMe extends Application {
  @Override public void start(final Stage stage) throws Exception {
    final Label response = new Label();
    final ImageView imageView = new ImageView(
      new Image("http://icons.iconarchive.com/icons/gianni-polito/colobrush/128/software-emule-icon.png")
    );
    final Button button = new Button("Save Me", imageView);
    button.setStyle("-fx-base: burlywood;");
    button.setContentDisplay(ContentDisplay.TOP);
    displayFlashMessageOnAction(button, response, "You have been saved!");

    layoutScene(button, response, stage);
    stage.show();

    setSaveAccelerator(button);
  }

  // sets the save accelerator for a button to the Ctrl+S key combination.
  private void setSaveAccelerator(final Button button) {
    Scene scene = button.getScene();
    if (scene == null) {
      throw new IllegalArgumentException("setSaveAccelerator must be called when a button is attached to a scene");
    }

    scene.getAccelerators().put(
      new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN), 
      new Runnable() {
        @Override public void run() {
          fireButton(button);
        }
      }
    );
  }

  // fires a button from code, providing visual feedback that the button is firing.
  private void fireButton(final Button button) {
    button.arm();
    PauseTransition pt = new PauseTransition(Duration.millis(300));
    pt.setOnFinished(new EventHandler<ActionEvent>() {
      @Override public void handle(ActionEvent event) {
        button.fire();
        button.disarm();
      }
    });
    pt.play();
  }

  // displays a temporary message in a label when a button is pressed, 
  // and gradually fades the label away after the message has been displayed.
  private void displayFlashMessageOnAction(final Button button, final Label label, final String message) {
    final FadeTransition ft = new FadeTransition(Duration.seconds(3), label);
    ft.setInterpolator(Interpolator.EASE_BOTH);
    ft.setFromValue(1);
    ft.setToValue(0);
    button.setOnAction(new EventHandler<ActionEvent>() {
      @Override public void handle(ActionEvent event) {
        label.setText(message);
        label.setStyle("-fx-text-fill: forestgreen;");
        ft.playFromStart();
      }
    });
  }

  private void layoutScene(final Button button, final Label response, final Stage stage) {
    final VBox layout = new VBox(10);
    layout.setPrefWidth(300);
    layout.setAlignment(Pos.CENTER);
    layout.getChildren().addAll(button, response);
    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;");
    stage.setScene(new Scene(layout));
  }

  public static void main(String[] args) { launch(args); }
}
// icon license: (creative commons with attribution) http://creativecommons.org/licenses/by-nc-nd/3.0/
// icon artist attribution page: (eponas-deeway) http://eponas-deeway.deviantart.com/gallery/#/d1s7uih

Sample output:

Sample program output

Update Jan 2020, using the same accelerator for multiple controls

One caveat for accelerators in current and previous implementations (JavaFX 13 and prior), is that you cannot, out of the box, define the same accelerator key combination for use on multiple menus or controls within a single application.

For more information see:

  • JavaFX ContextMenu accelerator firing from wrong tab
  • and the related JDK-8088068 issue report.

The linked issue report includes a work-around you can use to allow you define and use the same accelerator within multiple places within an application (for example on two different menu items in different context menus).

Note that this only applies to trying to use the same accelerator in multiple places within an application, if you don’t need try to do that, then you can ignore this information.

Leave a Comment

tech