I'm trying to create a notebook (in JavaFX) where the tabs have shortcuts much like buttons do. So, in the following example, there's a notebook with "Labor", "Parts" and "Tax".
The user can click on the tabs, of course, but they can also click Alt+L, Alt+P, Alt+T to select the labor, parts and tax panes respectively.
Looking at the docs, it appears the standard shortcut behavior comes from the Labeled
class, from which Tab does not descend. How do I get this behavior into my tabbed-pane tabs?
EDIT: To be more specific, I'm looking for the visual effect that matches the existing behavior and control code which does not require the controlling code to know what the hotkeys are.
So, as with buttons, the user presses the meta-key (CTRL, ALT, OPT, whatever) and the appropriate character underlines itself, when they press the key, the parent would probably have to search the text of the tabs to know which one to select.
You just need to register your own EventFilter
on the Scene
. This will allow you to listen for your desired keyboard combinations and react accordingly.
In the sample application below, you will pass the desired Tab
and KeyCombination
to a method which handles the registration of the EventFilter
.
In this example, I've configured the shortcuts of CTRL+1
, CTRL+2
, and CTRL+3
to select Tab
1, 2, or 3, respectively.
Note that you could also use KeyCombination.CONTROL_DOWN
instead of KeyCombination.SHORTCUT_DOWN
, but using SHORTCUT_DOWN
is preferred as it is platform independent.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TabPaneShortcuts extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// **********************************************************************************************
// Create a basic layout
// **********************************************************************************************
VBox root = new VBox(5);
root.setAlignment(Pos.TOP_CENTER);
root.setPadding(new Insets(10));
// **********************************************************************************************
// Create a TabPane
// **********************************************************************************************
TabPane tabPane = new TabPane();
// **********************************************************************************************
// Create the Tabs
// **********************************************************************************************
Tab tab1 = new Tab("Labor");
Tab tab2 = new Tab("Parts");
Tab tab3 = new Tab("Tax");
tabPane.getTabs().addAll(tab1, tab2, tab3);
// **********************************************************************************************
// Add the TabPane to our root layout
// **********************************************************************************************
root.getChildren().add(tabPane);
// **********************************************************************************************
// Set the Scene for the stage
// **********************************************************************************************
Scene scene = new Scene(root);
primaryStage.setScene(scene);
// **********************************************************************************************
// Register keyboard shortcuts to select Tabs with the keyboard. Here, we create the KeyCodeCombination
// to listen for CTRL + 1,2, or 3.
// **********************************************************************************************
registerShortcut(tabPane, tab1, scene,
new KeyCodeCombination(KeyCode.DIGIT1,
KeyCombination.SHORTCUT_DOWN));
registerShortcut(tabPane, tab2, scene,
new KeyCodeCombination(KeyCode.DIGIT2,
KeyCombination.SHORTCUT_DOWN));
registerShortcut(tabPane, tab3, scene,
new KeyCodeCombination(KeyCode.DIGIT3,
KeyCombination.SHORTCUT_DOWN));
// **********************************************************************************************
// Configure the Stage
// **********************************************************************************************
primaryStage.setWidth(300);
primaryStage.setHeight(200);
primaryStage.setTitle("Test Application");
primaryStage.show();
}
/**
* Registers the given KeyCombination to automatically select the given Tab of the TabPane
*
* @param tabPane The TabPane whose selection model we need to manipulate
* @param tab The Tab to be selected when the given KeyCombination is detected
* @param scene The main Scene on which to register this EventFilter
* @param combination The KeyCombination to listen for
*/
private void registerShortcut(TabPane tabPane, Tab tab, Scene scene, KeyCombination combination) {
// **********************************************************************************************
// Add an EventFilter to the Scene to listen for the given KeyCombination
// **********************************************************************************************
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
// **********************************************************************************************
// If the event matches the KeyCombination passed to this method, select the desired Tab
// **********************************************************************************************
if (combination.match(event)) {
tabPane.getSelectionModel().select(tab);
}
});
}
}
Your question specifically mentioned using ALT+[letter]
for the combination and that can be done easily as well:
registerShortcut(tabPane, tab1, scene,
new KeyCodeCombination(KeyCode.L,
KeyCombination.ALT_DOWN));
I chose to go with CTRL
simply because using ALT
causes my Windows 10 system to "ding" every time and I'm too lazy to figure out why. :)
Unfortunately (and a bit astonishingly to me), mnemonics on Tabs are not directly supported. Registering eventFilters as suggested in another answer is one way out. Another is to use the available support for mnemonics.
Doing something like:
Label label = new Label("_MyLabel");
label.setMnemonicParsing(true);
label.setLabelFor(otherControl);
will focus otherControl when the system detects a key combination as mnemonic (which basically is OS dependent).
Trying to apply that to Tabs runs into immediate problems:
Digging a bit into internals revealed interesting collaborators:
fire(ActionEvent)
addMnemonic(Mnemonic)
: on detecting the key combination of the given mnemonic, it fires an action event onto its nodeCombining all, we can use that mechanism to trigger an arbitrary action by mnemonic:
Label label = new Label("_MyLabel");
label.setMnemonicParsing(true);
label.addEventHandler(ActionEvent.ACTION, ac -> {
// do stuff
});
When applying that approach a TabPane, the label would be in a Tab's header and doStuff would select the Tab it resides in. As we still have no direct access to the label that is created by the skin, we could configure a Tab with graphic only and the graphic being a label as above:
Tab tab = new Tab();
tab.setGraphic(label);
// do stuff: tab.getTabPane().getSelectionModel().select(tab);
Another option would be to lookup the labels created by the skin and configure those in the same way, something like:
// building the tabs/tabPane as usual
Tab tab = new Tab("_MyTabText");
// .. after skin has been created
Set<Node> labels = tabPane.lookupAll(".tab-label");
labels.forEach(e -> {
if (e instanceof Label) {
Label label = (Label) e;
// beware: internal hierarchy!
Parent innerContainer = label.getParent();
Parent tabHeaderSkin = innerContainer.getParent();
// beware: implementation detail
Tab tab = (Tab) tabHeaderSkin.getProperties().get(Tab.class);
label.setMnemonicParsing(true);
label.addEventHandler(ActionEvent.ACTION, c -> {
tabPane.getSelectionModel().select(tab);
});
}
});
Both do work, but each has its drawback/s
On the bright side for both: no OS specifics in client code.
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