8

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

Sai Dandem
  • 8,229
  • 11
  • 26
  • This does not perform as described for me on my Windows 10 machine. It will flash the taskbar button, but does not bring the original window to the front. – Zephyr Jul 02 '19 at 01:11
  • @Zephyr, dont minimize the application. Let it keep open and switch to another window. And BTW, if you minimize also.. try the following scenario. Open the sub window.,. minimize the main stage... open notepad and start typing... when the sub window is closed, you cannot type in notepad.. because the main stage has gained the current focus (in taskbar button). – Sai Dandem Jul 02 '19 at 01:27
  • I don't minimize it. I let it run its course, switch to another application, and keep working. When the sub stage closes, my other application keeps focus and the Taskbar button for the original stage just flashes. – Zephyr Jul 02 '19 at 01:29
  • I am not sure how this happens to you, when you are actually focused on one application, but the task bar flashes another one. – Sai Dandem Jul 02 '19 at 01:34
  • That's the way it's supposed to be. Windows shouldn't take focus, but will flash the taskbar to alert the user that that application is requesting attention. I tried the exact scenario you posted and was able to continue typing in Notepad without a problem. – Zephyr Jul 02 '19 at 01:36
  • So you are confirming me that the main stage is not coming in front when there is another application in front of it.? I will try to attach a small gif file about the issue. – Sai Dandem Jul 02 '19 at 01:39
  • @Zephyr, Attached the gif file to demonstarte the issue. – Sai Dandem Jul 02 '19 at 01:47
  • Here is my [screencap](http://s000.tinyupload.com/download.php?file_id=58760585603762929743&t=5876058560376292974326240), showing no issue. So I'm not sure what the issue is with your workstation... – Zephyr Jul 02 '19 at 01:47
  • @Zephyr, Thanks for providing the screencap. Can I assume that this can be a JavaFX version issue. Currently I am using JavaFX8. I am not sure about your's .. is it a later one? – Sai Dandem Jul 02 '19 at 01:51
  • Yeah, I'm on JDK 1.8u211. It could be a bug in the older release, but I don't remember having the issue in the past either. – Zephyr Jul 02 '19 at 01:56
  • 1
    I'm on 1.8u112 with Windows 10, I got the same result as @Zephyr. You should include all the details about your environment, and see if anyone with similar environment could reproduce it. – Jai Jul 02 '19 at 02:17

1 Answers1

2

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 :)

Oleks
  • 1,011
  • 1
  • 14
  • 25