Java FX Waiting for User Input

2019-05-07 12:21发布

问题:

I'm writing an application in JavaFX and would like to create a function that waits for the user to enter text into my TextField and hit Enter before returning (continuing).

private void setupEventHandlers() {
    inputWindow.setOnKeyPressed(new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent e) {
            if (e.getCode().equals(KeyCode.ENTER)) {
                inputWindow.clear();
            }
        }
    });
}

Here I clear the text in my TextField when the user hits the Enter key.

Any ideas?

Edit: I'll clarify exactly what I'm looking for:

private void getInput() {
    do {
        waitForEventToFire();
    }
    while (!eventFired);
}

Obviously this is just pseudocode, but this is what I'm looking for.

回答1:

Sample Solution

Perhaps what you want to do is display a prompt dialog and use showAndWait to await for a response from the prompt dialog before continuing. Similar to JavaFX2: Can I pause a background Task / Service?

Likely your situation is a bit simpler than the background task service and (unless you have a long running task involved), you can just do everything on the JavaFX application thread. I created a simple sample solution which just runs everything on the JavaFX application thread

Here is the output of the sample program:

Every time some missing data is encountered, a prompt dialog is shown and awaits user input to fill in the missing data (user provided responses are highlighted in green in the screenshot above).

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;

public class MissingDataDemo extends Application {
  private static final String[] SAMPLE_TEXT = 
    "Lorem ipsum MISSING dolor sit amet MISSING consectetur adipisicing elit sed do eiusmod tempor incididunt MISSING ut labore et dolore magna aliqua"
    .split(" ");

  @Override public void start(Stage primaryStage) {
    VBox textContainer = new VBox(10);
    textContainer.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");

    primaryStage.setScene(new Scene(textContainer, 300, 600));
    primaryStage.show();

    TextLoader textLoader = new TextLoader(SAMPLE_TEXT, textContainer);
    textLoader.loadText();
  }

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

class TextLoader {
  private final String[] lines;
  private final Pane     container;

  TextLoader(final String[] lines, final Pane container) {
    this.lines = lines;
    this.container = container;
  }

  public void loadText() {
    for (String nextText: lines) {
      final Label nextLabel = new Label();

      if ("MISSING".equals(nextText)) {
        nextLabel.setStyle("-fx-background-color: palegreen;");

        MissingTextPrompt prompt = new MissingTextPrompt(
          container.getScene().getWindow()
        );

        nextText = prompt.getResult();
      }

      nextLabel.setText(nextText);

      container.getChildren().add(nextLabel);
    }              
  }

  class MissingTextPrompt {
    private final String result;

    MissingTextPrompt(Window owner) {
      final Stage dialog = new Stage();

      dialog.setTitle("Enter Missing Text");
      dialog.initOwner(owner);
      dialog.initStyle(StageStyle.UTILITY);
      dialog.initModality(Modality.WINDOW_MODAL);
      dialog.setX(owner.getX() + owner.getWidth());
      dialog.setY(owner.getY());

      final TextField textField = new TextField();
      final Button submitButton = new Button("Submit");
      submitButton.setDefaultButton(true);
      submitButton.setOnAction(new EventHandler<ActionEvent>() {
        @Override public void handle(ActionEvent t) {
          dialog.close();
        }
      });
      textField.setMinHeight(TextField.USE_PREF_SIZE);

      final VBox layout = new VBox(10);
      layout.setAlignment(Pos.CENTER_RIGHT);
      layout.setStyle("-fx-background-color: azure; -fx-padding: 10;");
      layout.getChildren().setAll(
        textField, 
        submitButton
      );

      dialog.setScene(new Scene(layout));
      dialog.showAndWait();

      result = textField.getText();
    }

    private String getResult() {
      return result;
    }
  }
}

Existing Prompt Dialog Library

There is a pre-written prompt dialog in the ControlsFX library which will handle the prompt dialog display for you.

Clarifying Event Handler Processing and Busy Waiting

You would like:

a function that waits for the user to enter text into my TextField and hit "enter".

By definition, that is what an EventHandler is. An EventHandler is "invoked when a specific event of the type for which this handler is registered happens".

When the event occurs, your event handler will fire and you can do whatever you want in the event handler - you don't need, and should never have a busy wait loop for the event.

Creating a TextField action event handler

Instead of placing the event handler on the window as you have in your question, it is probably better to use a specific action event handler on your text field using textField.setOnAction:

textField.setOnAction(
  new EventHandler<ActionEvent>() {
    @Override public void handle(ActionEvent e) {
      // enter has been pressed in the text field.
      // take whatever action has been required here.
    }
);

If you place the text field in a dialog with a default button set for the dialog, setting an event handler for the text field is unnecessary as the dialog's default button will pick up and process the enter key event appropriately.



回答2:

I would have liked to use ControlsFx, but I was not able to upgrade to a Java 1.8 runtime so had to build a dialog component from scratch. Here's what I came up with:

private static Response buttonSelected = Response.CANCEL;


/**
 * Creates a traditional modal dialog box
 *
 * @param owner       the calling Stage that is initiating the dialog.
 * @param windowTitle text that will be displayed in the titlebar
 * @param greeting    text next to icon that provides generally what to do (i.e. "Please enter the data below")
 * @param labelText   label text for the input box (i.e. "Number of widgets:")
 * @return If user clicks OK, the text entered by the user; otherwise if cancel, NULL.
 */
public static String prompt(final Stage owner, final String windowTitle, final String greeting, final String labelText) {
    //overall layout pane
    BorderPane root = new BorderPane();
    root.setPadding(PADDING);

    Scene scene = new Scene(root);

    final Dialog dial = new Dialog(windowTitle, owner, scene, MessageType.CONFIRM);

    final Button okButton = new Button("OK");
    okButton.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent e) {
            dial.close();
            buttonSelected = Response.YES;
        }
    });
    Button cancelButton = new Button("Cancel");
    cancelButton.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent e) {
            dial.close();
            buttonSelected = Response.NO;
        }
    });


    HBox headerGreeting = new HBox();
    headerGreeting.setSpacing(SPACING_SMALL);
    Text messageText = new Text(greeting);

    messageText.setFont(new Font(messageText.getFont().getName(), 14));
    headerGreeting.getChildren().addAll(icon, messageText);

    root.setTop(headerGreeting);

    //setup input controls
    HBox textHBox = new HBox(10);
    TextField input = new TextField();
    Label label = new Label();
    label.setText(labelText);
    label.setLabelFor(input);
    textHBox.getChildren().addAll(label, input);

    //create buttons
    HBox buttons = new HBox();
    buttons.setAlignment(Pos.CENTER);
    buttons.setSpacing(SPACING);
    buttons.getChildren().addAll(okButton, cancelButton);
    root.setCenter(buttons);

    //put controls and buttons in a vertical container, add to root component
    VBox container = new VBox(20);
    container.setPadding(new Insets(15, 12, 15, 12));
    container.getChildren().addAll(textHBox, buttons);
    root.setCenter(container);

    //handle enter key
    root.setOnKeyReleased(new EventHandler<KeyEvent>() {
        final KeyCombination combo = new KeyCodeCombination(KeyCode.ENTER);

        public void handle(KeyEvent t) {
            if (combo.match(t)) {
                okButton.fire();
            }
        }
    });

    input.requestFocus();   //set focus to the input box.

    dial.showDialog();
    if (buttonSelected.equals(Response.YES)) {
        return input.getText();
    }
    else { //cancel
        return null;
    }
}

My test harness looks like this, so you can run the above code fairly quickly:

import javafx.application.Application;
import javafx.stage.Stage;

public class FXOptionsPaneTest extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        String response = FXOptionsPane.prompt(primaryStage, "Create a new Study...", "Please enter the below information.", "Study Name:");
        System.out.println(response);
    }


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