Saturday, March 4, 2017

displaying a tooltip in javafx brings its stage into the foreground

Leave a Comment

I am trying to work around this bug in the jdk: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8088624

public class Blubb extends Application {     public static void main(String[] args) {         launch(args);     }      @Override     public void start(Stage primaryStage) throws Exception {         Button btn = new Button("Click");         btn.setTooltip(new Tooltip("Blubb"));          Scene scene = new Scene(new BorderPane(btn), 320, 240);         primaryStage.setScene(scene);         primaryStage.show();          Stage secondStage = new Stage();         secondStage.setScene(new Scene(new BorderPane(new Button("Click")), 320, 240));         //secondStage.initOwner(primaryStage);         secondStage.show();     } } 

If the button on the primary stage is hovered, it will come in front of the second stage. I found that calling initOwner() on a Stage will eliminate this behavior.

Now my problem is following: I have multiple "popups" that have a common owner (the primary stage). Hovering over controls on the primary stage doesn't cause any unexpected behavior after the initOwner() workaround. If you however hover over controls in a popup while another popup was in focus, the hovered popup will steal focus.

Is there a way I can work around this bug for not only the primary stage but also the popups?

UPDATE: turns out my workaround has undesired side-effects. Javadocs for Stage state following:

A stage will always be on top of its parent window.

So additionally, what would be a workaround that makes the popup not "always on top" and minimizable?

3 Answers

Answers 1

There is a way to get around it by overlaying StackPanes. Create your Scene with a StackPane so that you can add another StackPane when the stage has lost its focus. The overlayed pane will prevent Tooltips or anything else happening on mouse-over while the pane is not in focus. You may also minimize any of your stages and they won't be always-on-top.

public class Blubb extends Application {      public static void main(String[] args) {         launch(args);     }      @Override     public void start(Stage primaryStage) throws Exception {         Button button_1 = new Button("Button #1");         button_1.setTooltip(new Tooltip("Blubb #1"));          StackPane primary = new StackPane(new BorderPane(button_1));         primaryStage.setScene(new Scene(primary, 320, 240));         addStageFocusListener(primaryStage, primary);         primaryStage.show();          Button button_2 = new Button("Button #2");         button_2.setTooltip(new Tooltip("Blubb #2"));          StackPane second = new StackPane(new BorderPane(button_2));         Stage secondStage = new Stage();         addStageFocusListener(secondStage, second);         secondStage.setScene(new Scene(second, 320, 240));         secondStage.show();     }      public void addStageFocusListener(Stage stage, StackPane stackPane) {         stage.focusedProperty().addListener(new ChangeListener<Boolean>(){             public final StackPane preventTooltip = new StackPane();             public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {                 if(stage.isFocused()) {                     if(stackPane.getChildren().contains(preventTooltip)) {                         stackPane.getChildren().remove(preventTooltip);                     }                 } else {                     stackPane.getChildren().add(preventTooltip);                 }             }         });     } } 

Answers 2

You can try this:

public static final disableMouseEventOnUnfocus(final Stage stage) {     if (stage == null         || stage.getScene() == null         || stage.getScene().getRoot() == null)         return;     else     {         stage.getScene().getRoot().mouseTransparentProperty().bind(stage.focusedProperty().not());     } } 

I didn't try it though, but if it works, this should be a good alternative. There is no need to restructure your layout, and you can leave all your layout in FXML, without specifying fx:id for the tooltips.

Answers 3

You could try to unset the tooltip whenever the node's window loses focus. Such as below:

public class Blubb extends Application {     public static void main(String[] args) {         launch(args);     }      public static void installTooltip(Node n, Tooltip tp)     {         Window w = n.getScene().getWindow();         w.focusedProperty().addListener((val, before, after) -> {             if (after)                 Tooltip.install(n, tp);             else                 Tooltip.uninstall(n, tp);         });          if (w.isFocused())             Tooltip.install(n, tp);         else             Tooltip.uninstall(n, tp);     }      @Override     public void start(Stage primaryStage) throws Exception {         Tooltip tp = new Tooltip("Blubb");         Button btn = new Button("Click");          Scene scene = new Scene(new BorderPane(btn), 320, 240);         primaryStage.setScene(scene);         //primaryStage.show();          Stage secondStage = new Stage();         secondStage.setScene(new Scene(new BorderPane(new Button("Click")), 320, 240));         //secondStage.initOwner(primaryStage);         secondStage.show();         primaryStage.show();          installTooltip(btn, tp);     } } 

Of course, you would have to call installTooltip after the node is added to the component.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment