Отображение всплывающей подсказки в javafx приводит к выходу на передний план

Я пытаюсь обойти эту ошибку в 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();
    }
}

Если кнопка на основной ступени зависнет, она появится перед вторым этапом. Я обнаружил, что вызов initOwner() на сцене устраняет это поведение.

Теперь моя проблема следующая: у меня есть несколько "всплывающих окон", которые имеют общего владельца (основной этап). Наведение курсора на элементы управления на первичной стадии не вызывает неожиданного поведения после обходного пути initOwner(). Если вы, однако, нависаете над элементами управления во всплывающем окне, а другое всплывающее окно находилось в фокусе, зависающее всплывающее окно будет красть фокус.

Есть ли способ обойти эту ошибку не только для основного этапа, но и для всплывающих окон?

UPDATE. Оказывается, у моего обходного пути есть нежелательные побочные эффекты. Javadocs для состояния Stage:

Этап всегда будет находиться над его родительским окном.

Итак, что было бы обходным путем, которое делает всплывающее окно "всегда сверху" и минимизируемым?

Ответы

Ответ 1

Есть способ обойти его, наложив StackPanes. Создайте Scene с помощью StackPane, чтобы добавить еще один StackPane, когда эта сцена потеряла фокус. Наложенная панель предотвратит Tooltip или что-то еще, что происходит при наведении мыши, пока панель не находится в фокусе. Вы также можете свести к минимуму любые ваши этапы, и они не будут всегда на высоте.

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);
                }
            }
        });
    }
}

Ответ 2

Вы можете попробовать следующее:

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());
    }
}

Я не пробовал, но если это работает, это должна быть хорошая альтернатива. Нет необходимости реструктурировать макет, и вы можете оставить все свои макеты в FXML, не указав fx:id для всплывающих подсказок.

Ответ 3

Я придумал это альтернативное решение, поскольку мне было легче в моем случае подклассифицировать подсказку и применить исправление. Я просто перегружаю метод show(), чтобы показать, только ли окно с привязкой. Он работает как прелесть для меня...

public class FixedTooltip extends Tooltip {

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

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

}

Ответ 4

Вы можете попытаться отключить всплывающую подсказку, когда окно node теряет фокус. Например, ниже:

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);
    }
}

Конечно, вам нужно будет вызвать installTooltip после добавления node к компоненту.