Sai Dandem
Sai Dandem

Reputation: 9989

JavaFX : How to close a sub window without getting focus on main window

I am trying to close a sub window programatically after certain time. This sub window initOwner is set with main stage. But on closing this sub window, the main window is getting focused. Is there any way to close the sub window(programatically) without gaining focus on main window?

Below is the quick demo of my issue. I tried all the possible ways to close the window. Steps to reproduce:

  1. After starting the application, click the button to open the sub window. This sub window will close automatically after 10seconds.

  2. Meanwhile open any other application (notepad, outlook, browser.. or whatever). While you are working on that application, when the sub window is closed, the main stage gets focus and comes in front of my current application. This is quite annoying to my client.

Note: I cannot remove initOwner(), as I always want to keep my sub window on top of the main window.

Update : Based on the comments, I tried running the demo with different jdk versions (u91, u121 & u211) and in Windows 10. In all three cases , the moment the sub window is closed, the main stage is coming to front. I even tried in a differnt system but the results are same :(

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;

public class OwnerStage_Demo extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Button button = new Button("Open Window");
        button.setOnAction(e -> {
            Stage stg = new Stage();
            stg.setScene(new Scene(new StackPane(), 300, 300));
            stg.initOwner(stage);
            stg.show();
            // Window will close automatically after 10secs.
            Timeline timeline = new Timeline(new KeyFrame(Duration.millis(10000), x -> {
                //stg.close();
                //stg.hide();
                stg.fireEvent(new WindowEvent(stg, WindowEvent.WINDOW_CLOSE_REQUEST));
            }));
            timeline.setCycleCount(1);
            timeline.play();
        });
        VBox root = new VBox(button);
        root.setSpacing(10);
        Scene sc = new Scene(root, 600, 600);
        stage.setScene(sc);
        stage.show();
    }

    public static void main(String... a) {
        Application.launch(a);
    }
}

Update : Attached the gif demonstrating the issue. enter image description here

Upvotes: 8

Views: 1517

Answers (1)

Oleks
Oleks

Reputation: 1051

How a window gains the focus depends on a platform (OS + JRE). The platform processes focused window that is why the window may have different behavior on different OS after calling focus request.

There is no way to achieve required behaviour with pure JFX because of the restriction you had set:

Note: I cannot remove initOwner(), as I always want to keep my sub window on top of the main window.

com.sun.javafx.tk.quantum.WindowStage

if (!isPopupStage && owner != null && owner instanceof WindowStage) {
    WindowStage ownerStage = (WindowStage)owner;
    ownerStage.requestToFront();
}

What you can do is to imitate owner window <- child window relationship without initializing real owner.

Source:

public class PlainZStage extends Stage {

    public PlainZStage(final Window owner) {
        init(owner, this::focusedChanged);
    }

    private void init(final Window owner, final ChangeListener<Boolean> listener) {
        showingProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(final ObservableValue<? extends Boolean> observable, final Boolean oldValue,
                    final Boolean newValue) {
                owner.getScene().getRoot().setDisable(newValue);
                if (newValue) {
                    owner.focusedProperty().addListener(listener);
                } else {
                    owner.focusedProperty().removeListener(listener);
                    showingProperty().removeListener(this);
                }
            }
        });
    }

    private void focusedChanged(final ObservableValue<? extends Boolean> source, final Boolean oldValue,
            final Boolean newValue) {
        if (newValue && isShowing()) {
            toFront();
        }
    }
}

Usage:

button.setOnAction(e -> {
    final Stage stg = new PlainZStage(stage);
    stg.setScene(new Scene(new StackPane(), 300, 300));
    stg.show();
    // Window will close automatically after 10secs.
    final Timeline timeline = new Timeline(new KeyFrame(Duration.millis(10000), x -> {
        stg.close();
    }));

Alternatively, you could combine JFX&SWING to filter focus events but you will face pure architectural evil :)

Upvotes: 2

Related Questions