3

I have a TabPane with two tabs, each with a TableView which has a context menu. The two context menus have duplicate accelerators, but I expect only the currently selected tab to respond. But what happens is only the last added Tab seems to get the event, even if it's not selected. Below is a complete sample code:

package sample;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCombination;
import javafx.stage.Stage;

public class Main extends Application {

@Override
public void start(Stage primaryStage) throws Exception{
    Tab t1 = new Tab("Tab 1");
    TableView<Void> tv1 = new TableView<>();
    t1.setContent(tv1);
    MenuItem mi1 = new MenuItem("Action 1");
    mi1.setAccelerator(KeyCombination.valueOf("F3"));
    mi1.setOnAction(event->System.out.println("Action 1!"));
    ContextMenu ctx1 = new ContextMenu(mi1);
    tv1.setContextMenu(ctx1);

    Tab t2 = new Tab("Tab 2");
    TableView<Void> tv2 = new TableView<>();
    t2.setContent(tv2);
    MenuItem mi2 = new MenuItem("Action 2");
    mi2.setAccelerator(KeyCombination.valueOf("F3"));
    mi2.setOnAction(event->System.out.println("Action 2!"));
    ContextMenu ctx2 = new ContextMenu(mi2);
    tv2.setContextMenu(ctx2);

    TabPane tabPane = new TabPane(t1, t2);
    tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
    primaryStage.setScene(new Scene(tabPane));
    primaryStage.show();
}


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

I expect "Action 1!" to be printed when Tab 1 is selected, and "Action 2!" to be printed when Tab 2 is selected, but in reality "Action 2!" is printed regardless of which tab is selected. How do I solve this, so the correct action is performed depending on which tab (TableView) is currently visible?

Itai
  • 6,641
  • 6
  • 27
  • 51
  • 1
    hmm .. accelerators are unique per application (afaik, not entirely certain, though). A way out might be to set/unset the accelerators on the menuItems in a selection listener to the tabPane. – kleopatra Dec 10 '15 at 10:02
  • Thanks @kleopatra , I feared it might be something like this. Do you have a link or a source about this? I tried to find something on how Accelerators work in JavaFX, but couldn't find anything concise. – Itai Dec 10 '15 at 10:09

1 Answers1

4

I guess you've come across https://bugs.openjdk.java.net/browse/JDK-8088068 (see there for a workaround). JavaFX ist not really prepared for the same accelerator to be installed in multiple MenuItems.

rli
  • 1,745
  • 1
  • 14
  • 25
  • No, I haven't come across this yet. I'll look into it. It's pretty depressing sometimes, seeing how backwards JavaFX is. – Itai Dec 10 '15 at 10:15
  • Ok, the workaround suggested in the link seems to have solved my problem. – Itai Dec 10 '15 at 10:26
  • Indeed a bug - I was wrong in expecting a unique accelerator per application in a _contextMenu_ ;-) And a good workaround! Whether to use the released or pressed event might depend on OS - on win7 I had to use pressed. – kleopatra Dec 10 '15 at 12:27
  • 2
    curious: why do you search for the focused node (vs. using scene.getFocusOwner)? – kleopatra Dec 10 '15 at 12:37
  • Ignorance. I didn't know about scene.getFocusOwner(). This makes for a far better workaround as scene.getFocusOwner() is O(1) instead of O(n) (where n is the number of nodes in the scene). Thanks. – rli Dec 14 '15 at 06:41
  • An aside (@sillyfly as well) : had been irritated by my incorrect assumption ... turned out to an old memory from Swing - accelerators in context menus (aka: JPopupMenu) don't work at all (https://docs.oracle.com/javase/tutorial/uiswing/components/menu.html) BTW: I'm not notified by comments unless they contain `@mynick` - just saw your last accidentally. – kleopatra Dec 15 '15 at 12:12
  • I still have only one problem with this workaround - while it does indeed fire for the correct control if it is focused, when the focused control doesn't have a matching accelerator the event still gets fired for the control which does have it and was added last to the Scene Graph. Is there a way to overcome this? Maybe iterate over the entire scene graph and add an invisible control with every possible accelerator that exists in the scene graph? That feels excessive, but is there a better way? – Itai Dec 15 '15 at 19:18
  • Also paging @kleopatra - maybe you'll have an idea. – Itai Dec 15 '15 at 19:42
  • 1
    @sillyfly no, there is nothing out of the box: the accelerators of the menuItems of a contextMenu are registered when calling someControl.setContextMenu(..). And I can't think of a way to unregister all other bindings (dont know them) at any time. – kleopatra Dec 15 '15 at 22:45