FULL PRODUCT VERSION :
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [version 10.0.10586]
A DESCRIPTION OF THE PROBLEM :
When you create a WINDOW_MODAL dialog while it's owner-window is minimized, and you then restore the owner window (click on task bar icon), the owner window pops up, but the dialog remains invisible :
Now I know what happens : it's actually rendered off-screen, as the reported coordinates of minimized owner window are something like x = -32765, y = -32765
I have designed a fix using a listener on owner windows's x property and another on its iconified property to recenter the dialog when its deiconified based on its actual coordinates
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a scene containing at least two focusable controls (such as a TextFields for instance)
2. Set it as root for a new stage (Application's primaryStage for instance)
3.a. Add a handler for WindowEvent.ON_CLOSE_REQUEST on that stage
3.b. In this handler, make an Alert, set it's modality to WINDOW_MODAL and its owner as the stage, call its showAndWait() method
4. Launch application, minimize the main window, right-click its taskbar icon, and select "Close Window"
5. Restore the main window by clicking its icon on task bar : its frozen. You can't get focus into the TextField, can't minimize, maximize or close it
6. Press ESCAPE: window unfrozen (the alert has been closed)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The dialog should pop up in front of the owner window when it's restored, so you could read and close it and get back control over your application
ACTUAL -
The dialog is invisible, and the owner window is frozen: you can't get focus into the TextField, can't minimize, maximize or close it
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package application;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
TextField tf1 = new TextField();
tf1.setPrefColumnCount(10);
TextField tf2 = new TextField();
tf2.setPrefColumnCount(10);
VBox vb = new VBox(4, tf1, tf2);
vb.setFillWidth(false);
vb.setAlignment(Pos.CENTER);
root.setCenter(vb);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, (event) -> {
Alert alert = new Alert(AlertType.INFORMATION, "Close window ?", ButtonType.YES, ButtonType.NO);
alert.initModality(Modality.WINDOW_MODAL);
alert.initOwner(primaryStage);
alert.showAndWait().ifPresent((result) -> {
if (result == ButtonType.NO)
event.consume();
});
});
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
public T safeShowAndWait(Dialog<T> dialog) {
Stage nfstage = null;
if (dialog.getOwner() instanceof Stage)
nfstage = (Stage) dialog.getOwner();
Stage ostage = nfstage;
ChangeListener<? super Boolean> listener = (observable, oldValue, newValue) -> {
System.err.println(observable + ": " + oldValue + " -> " + newValue);
if (dialog == null || dialog.getDialogPane() == null || dialog.getDialogPane().getScene() == null)
return;
if (!newValue) { // Owner window is being de-iconified
Window w = dialog.getDialogPane().getScene().getWindow();
if (w instanceof Stage && dialog.getOwner() instanceof Stage) {
Stage stage = (Stage) dialog.getOwner();
Stage wStage = (Stage) w;
//Get all screens displaying part of the dialog
List<Screen> scrs = Screen.getScreensForRectangle(wStage.getX(), wStage.getY(), wStage.getWidth(), wStage.getHeight());
if (scrs.size() == 0) { //No screen found : dialog is off-screen
//Center dialog on owner window
double x = stage.getX() + (stage.getWidth() - wStage.getWidth()) / 2.0;
double y = stage.getY() + (stage.getHeight() - wStage.getHeight()) / 2.0;
//Apply coordinates
wStage.setX(x);
wStage.setY(y);
}
}
}
};
ChangeListener<? super Number> xListener = (observable, oldValue, newValue) -> {
//Detect de-iconification when window is maximized using value of stage.xProperty();
if (oldValue == null || newValue == null)
return;
if (ostage.isMaximized()) {
if (newValue.doubleValue() <= -32000 && oldValue.doubleValue() > -32000)
Platform.runLater(() -> ostage.setIconified(true));
else if (newValue.doubleValue() > -32000 && oldValue.doubleValue() <= -32000)
Platform.runLater(() -> ostage.setIconified(false));
}
};
try {
if (ostage != null) {
ostage.iconifiedProperty().addListener(listener);
ostage.xProperty().addListener(xListener);
}
Optional<T> bType = dialog.showAndWait();
if (bType.isPresent())
return bType.get();
return null;
} finally {
if (ostage != null) {
ostage.iconifiedProperty().removeListener(listener);
ostage.xProperty().removeListener(xListener);
}
}
}