Home » Java » JavaFX Opening one dialog from another

JavaFX Opening one dialog from another

Posted by: admin December 28, 2021 Leave a comment

Questions:

I have the following workflow in my application that is causing a problem:

Click Button to Open Dialog > Open Dialog > Click Button From Dialog > Display Confirmation Alert > Upon Confirmation close the first dialog and open a new dialog

The second dialog does not allow input into the TextField. I have included a SSCE that displays the problem. One additional weird thing is that if you try to close the second dialog by clicking the ‘X’, and then close the Alert then I am able to type into the field.

public class DialogTest extends Application {

  @Override
  public void start(Stage primaryStage) {
    Button button = new Button("Show Dialog");

    VBox root = new VBox(10, button);
    root.setAlignment(Pos.CENTER);

    Scene scene = new Scene(root, 350, 120);
    primaryStage.setScene(scene);
    primaryStage.show();

    button.setOnAction(event -> {
      Dialog<Pair<String, String>> dialog = getDialog(scene.getWindow(), "Dialog 1", true);
      dialog.showAndWait();
    });
  }

  public static void main(String[] args) {
    launch(args);
  }

  public Dialog<Pair<String, String>> getDialog(Window owner, String title, boolean addButton) {
    Dialog<Pair<String, String>> dialog = new Dialog<>();
    dialog.setTitle(title);
    dialog.initOwner(owner);
    dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);

    if(addButton) {
      Button button = new Button("Show Dialog");
      dialog.getDialogPane().setContent(button);
      button.setOnAction(event -> {
        Alert alert = new Alert(AlertType.CONFIRMATION, "Are you sure?", ButtonType.YES, ButtonType.NO);
        alert.initOwner(owner);
        if(alert.showAndWait().get() == ButtonType.YES) {
          dialog.close();
          Dialog<Pair<String, String>> dialog2 = getDialog(owner, "Dialog 2", false);
          TextField textField = new TextField();
          dialog2.getDialogPane().setContent(textField);
          dialog2.getDialogPane().getScene().getWindow().setOnCloseRequest(closeEvent -> {
            closeEvent.consume();
            if(textField.getText().trim().isEmpty()) {
              Alert alert2 = new Alert(AlertType.ERROR, "Please enter a value", ButtonType.OK);
              alert2.initOwner(dialog2.getDialogPane().getScene().getWindow());
              alert2.showAndWait();
            }
          });
          dialog2.showAndWait();
        }
      });
    }

    return dialog;
  }
}
Answers:

Problem

As explained, you have a modality problem.

Solution

The following code will demonstrate a solution where the user is asked if he really wants to print and after printing, if the ending number is correct.

(Note, that I use a class IntField from here)

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.Window;

public class DialogTest extends Application {

    Region veil;
    ProgressIndicator indicator;

    IntField startingNumber = new IntField(0, 999999, 0);
    IntField endingNumber = new IntField(startingNumber.getValue(), 999999, startingNumber.getValue() + 1);
    ButtonType printButtonType = new ButtonType("Print", ButtonData.OK_DONE);
    Stage stage;

    @Override
    public void start(Stage primaryStage) {
        stage = primaryStage;
        Button button = new Button("Print Checks");

        VBox box = new VBox(10, button);
        box.setAlignment(Pos.CENTER);

        veil = new Region();
        veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.3);");
        veil.setVisible(false);

        indicator = new ProgressIndicator();
        indicator.setMaxHeight(60);
        indicator.setMinWidth(60);
        indicator.setVisible(false);

        StackPane root = new StackPane(box, veil, indicator);

        root.setAlignment(Pos.CENTER);

        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();

        button.setOnAction((event) -> {
            Dialog<ButtonType> dialog
                    = getCheckPrintDialog(primaryStage, "Enter starting check number");
            dialog.showAndWait()
                    .filter(result -> result == printButtonType)
                    .ifPresent(result -> {
                        // this is for this example only, normaly you already have this value
                        endingNumber.setValue(startingNumber.getValue() + 1);
                        printChecks(startingNumber.getValue(), endingNumber.getValue());
                    });
        });
    }

    public static void main(String[] args) {
        launch(args);
    }

    public <R extends ButtonType> Dialog getCheckPrintDialog(Window owner, String title) {
        Dialog<R> dialog = new Dialog<>();
        dialog.initOwner(owner);
        dialog.setTitle(title);
        dialog.getDialogPane().getButtonTypes().addAll(printButtonType, ButtonType.CANCEL);

        Button btOk = (Button) dialog.getDialogPane().lookupButton(printButtonType);
        btOk.addEventFilter(ActionEvent.ACTION, event -> {
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Print Checks? Are you sure?", ButtonType.YES, ButtonType.NO);
            alert.showAndWait()
                    .filter(result -> result == ButtonType.NO)
                    .ifPresent(result -> event.consume());
        });

        GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);

        Text from = new Text("Starting Number:");
        grid.add(from, 0, 0);

        grid.add(startingNumber, 1, 0);

        dialog.getDialogPane().setContent(grid);
        return dialog;
    }

    private void printChecks(int from, int to) {

        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                Thread.sleep(5000);
                return null;
            }
        };

        task.setOnSucceeded((event) -> {
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Has the last check, the number: " + endingNumber.getValue() + "?", ButtonType.YES, ButtonType.NO);
            alert.initOwner(stage);
            Button btnNo = (Button) alert.getDialogPane().lookupButton(ButtonType.NO);
            btnNo.addEventFilter(ActionEvent.ACTION, e -> {
                Dialog<ButtonType> newEndNum = new Dialog<>();
                newEndNum.setTitle("Enter the ending check number");
                newEndNum.initOwner(stage);
                newEndNum.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
                GridPane grid = new GridPane();
                grid.setHgap(10);
                grid.setVgap(10);

                Text toUser = new Text("Ending Number:");
                grid.add(toUser, 0, 0);

                grid.add(endingNumber, 1, 0);

                newEndNum.getDialogPane().setContent(grid);
                newEndNum.showAndWait().filter(result -> result == ButtonType.CANCEL)
                        .ifPresent(result -> e.consume());
            });
            alert.showAndWait();
        });
        veil.visibleProperty().bind(task.runningProperty());
        indicator.visibleProperty().bind(task.runningProperty());
        new Thread(task).start();
    }

}

Working Application

  1. The main Window:

Main

  1. The Print Dialog:

PrintDialog

  1. After a click on Print (Alerts are localized, for me in german):

Confirm

  1. After a click on Yes the Print Dialog closes and a progress will be visible (for 5 sec. in this example)

Progress

  1. After the Printing finishes a Dialog comes up which is asking for the correct ending number

Confirm2

  1. If you click Yes all is done, if you click No another dialog opens to enter the correct ending value

EndingNumber

###

I have found a point where the problem is. But because I am just beginning with JavaFx I cannot provide the “why”.
It seems to me that the problem is in the dialog.close(), just after the if (alert.showAndWait().get() == ButtonType.YES).
Look like it looses some reference to the dialog when you close it or something like that (I let the experts clear this out).

As a workaround, and it works for me, is move the dialog.close() to after dialog2.showAndWait();

public Dialog<Pair<String, String>> getDialog(Window owner, String title, boolean addButton) {
        Dialog<Pair<String, String>> dialog = new Dialog<>();
        dialog.setTitle(title);
        dialog.initOwner(owner);
        dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);

        if (addButton) {
            Button button = new Button("Show Dialog");
            dialog.getDialogPane().setContent(button);
            button.setOnAction(event -> {
                Alert alert = new Alert(AlertType.CONFIRMATION, "Are you sure?", ButtonType.YES, ButtonType.NO);
                alert.initOwner(owner);
                if (alert.showAndWait().get() == ButtonType.YES) {
                    // dialog.close(); // supressed this and placed at the bottom
                    Dialog<Pair<String, String>> dialog2 = getDialog(owner, "Dialog 2", false);
                    TextField textField = new TextField();
                    dialog2.getDialogPane().setContent(textField);
                    dialog2.getDialogPane().getScene().getWindow().setOnCloseRequest(closeEvent -> {
                        closeEvent.consume();
                        if (textField.getText().trim().isEmpty()) {
                            Alert alert2 = new Alert(AlertType.ERROR, "Please enter a value", ButtonType.OK);
                            alert2.initOwner(dialog2.getDialogPane().getScene().getWindow());
                            alert2.showAndWait();
                        }
                    });
                    dialog2.showAndWait();
                    dialog.close(); // new location
                }
            });
        }

        return dialog;
    }

The reasons for this to happen I cannot explain, but this could be a workaround.
I hope this helps you.

###

In your “start” method where you create Dialog1 you should call dialog.show() instead of dialog.showAndWait().

i.e.

    button.setOnAction(event -> {
        Dialog<Pair<String, String>> dialog = getDialog(scene.getWindow(), "Dialog 1", true);
        // dialog.showAndWait();
        dialog.show();
    });