Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

displaying a tooltip in javafx brings its stage into the foreground

Tags:

java

javafx

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?

like image 676
Selim Avatar asked Feb 23 '17 13:02

Selim


4 Answers

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);
                }
            }
        });
    }
}
like image 121
Matthew Wright Avatar answered Nov 08 '22 14:11

Matthew Wright


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.

like image 34
Jai Avatar answered Nov 08 '22 14:11

Jai


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.

like image 1
theKidOfArcrania Avatar answered Nov 08 '22 15:11

theKidOfArcrania


I've come up with this alternative solution, as I've found it easier in my case to subclass Tooltip and apply a fix there. I just overload the show() method to only show if the owning window is focused. It's working like a charm for me...

public class FixedTooltip extends Tooltip {

    public FixedTooltip(String string) {
        super(string);
    }

    @Override
    protected void show() {        
        Window owner = getOwnerWindow();        
        if (owner.isFocused())
            super.show(); 
    }    

}
like image 3
Raymond Nagel Avatar answered Nov 08 '22 13:11

Raymond Nagel