CrashTestDummy
CrashTestDummy

Reputation: 51

How to restrict user input on a text area to match a specific string in JavaFX?

I need to remember about 3 pages of text verbatim, including punctuation. I'm attempting to build a simple JavaFX program to quiz myself on this material.

I'd like to use a text area for the user to type input. The user is to type into the text area - if the input correctly matches the existing string (the stuff I need to remember), the text is allowed as input into the text field. If they make a mistake, the input simply isn't recognized and no incorrect input will appear in the text area.

What's the best approach for something like this? I'm pretty new to JavaFX and Java in general. I've got my UI set up in JavaFX, I'm just unsure how to implement the check on the text area. Thank you.

Upvotes: 0

Views: 453

Answers (1)

Slaw
Slaw

Reputation: 46255

One option is to use a TextFormatter that only allows the new text if it resembles the expected text. Here's a proof-of-concept:

import java.util.Objects;
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class App extends Application {

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

  @Override
  public void start(Stage primaryStage) {
    TextArea area = new TextArea();
    area.setPrefRowCount(5);
    area.setPrefColumnCount(25);
    area.setTextFormatter(new TextFormatter<>(new ExpectedTextFilter("Hello, World!")));

    StackPane root = new StackPane(area);
    root.setPadding(new Insets(10));

    primaryStage.setScene(new Scene(root));
    primaryStage.show();
  }

  private static class ExpectedTextFilter implements UnaryOperator<Change> {

    private final String expectedText;

    ExpectedTextFilter(String expectedText) {
      this.expectedText = Objects.requireNonNull(expectedText);
    }

    @Override
    public Change apply(Change change) {
      if (change.isContentChange()) {
        if (change.isReplaced()) {
          // simply don't allow replacements
          return null;
        } else if (change.isDeleted()) {
          // only allow deletions from the end of the control's text
          return change.getRangeEnd() == change.getControlText().length() ? change : null;
        } else {
          return expectedText.startsWith(change.getText(), change.getRangeStart()) ? change : null;
        }
      }
      return change;
    }
  }
}

The above uses this constructor of TextFormatter which accepts a UnaryOperator. This operator, known as the "filter", can intercept changes to the text input control and either allow the change as is, alter it, or reject it altogether:

The filter itself is an UnaryOperator that accepts TextFormatter.Change object. It should return a TextFormatter.Change object that contains the actual (filtered) change. Returning null rejects the change [emphasis added].

Upvotes: 3

Related Questions