6

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;
  }
}
Tommo
  • 977
  • 14
  • 35
  • I didn't noticed any wierd behavior, exept that TextField on second dialog doesn't show inserted text (but you can type there, and getText() is returning proper String). I cannot determine the cause of this TextField bug. The second dialog won't close after clicking X button, because you consumed this event. Last alert won't show if you inserted some text into text field (which you don't see) – Tomasz Jagiełło Sep 17 '15 at 21:13
  • @tomasz77 Yes, the issue with the text not showing in the TextField is the problem I was talking about. Though upon clicking the X and then closing the dialog that says "Please enter a value", I am able to type into the field. – Tommo Sep 17 '15 at 22:50

3 Answers3

8

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

Community
  • 1
  • 1
aw-think
  • 4,723
  • 2
  • 21
  • 42
  • Some clarification while on mobile. Basically the application runs a routine to print a series of checks. We provide the starting check number, from there if the user clicks OK then we prompt to make sure they are sure they want to run the routine, and then upon completion we display another dialog for the user to enter the ending check number. – Tommo Sep 23 '15 at 20:14
  • Sorry, I've been out of the office this past week. So the solution seems to be missing the part where after asking if the user is sure, another dialog displays to verify that the ending check number that printed matches the intended ending check number that was previously entered. This is the dialog that seems to prevent input into the field. – Tommo Sep 28 '15 at 18:58
  • Hm, maybe I don't understand it. So you want a dialog where the user enter a starting number and after this dialog will be closed you need a second dialog where the user enter an ending number. After that you want him to confirm that he wants to print the checks? This is not ergonomic! Why not ask the user for starting and ending once (in one Dialog) and only proof the ranges of number (starting < ending etc.) and then ask the user once if he really wants to print? – aw-think Sep 28 '15 at 19:51
  • Hopefully this is a better explanation. We prompt user for starting number, then ask user if they are sure they want to start printing, then display a dialog for the user to enter the ending number. The logic is that we want to make sure all checks printed correctly. We know what the ending check number should be, but ask the user to confirm this once check printing has completed to make sure all checks printed. – Tommo Sep 28 '15 at 21:04
  • I've updated my answer. Because printing should be a non blocking statement, you probably open a new Dialog after the printing process finishes. – aw-think Sep 29 '15 at 05:21
4

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.

2

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();
    });
Amber
  • 2,413
  • 1
  • 15
  • 20
  • Well, in my application I need processing to wait until a response is received from the dialog, which usually requires the showAndWait() call since it's blocking. – Tommo Sep 21 '15 at 19:16