1

I am working on a JavaFX project and I have a problem using the TextField control. I want to limit the characters that users will enter to each TextField to one. I found a solution if you use a single textfield with a Listener:

public static void addTextLimiter(final TextField tf, final int maxLength) {
tf.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(final ObservableValue<? extends String> ov, final String oldValue, final String newValue) {
        if (tf.getText().length() > maxLength) {
            String s = tf.getText().substring(0, maxLength);
            tf.setText(s);
        }
    }
});

But the problem is that I have an Array of TextFields. Do you guys maybe know how I can rewrite this listener for a TextFieldArray?

Array list implementation:

static public TextField[] tfLetters = new TextField[37];

Initialisation of the array:

private void layoutNodes() {
    int letternummer = 0;
    for (int i = 1; i < 8; i++) {
        for (int j = 0; j < i + 1; j++) {
            this.tfLetters[letternummer] = new TextField("Letter " + i);
            this.add(tfLetters[letternummer], j, i);
            tfLetters[letternummer].setPadding(new Insets(5, 30, 5, 5));
            tfLetters[letternummer].setAlignment(Pos.CENTER);
            tfLetters[letternummer].setMinSize(10, 10);
            letternummer++;
        }

    }

I used the given solution:

Arrays.asList(tfLetters).forEach(tfLetters -> GamePresenter.addTextLimiter(tfLetters,1));

GamePresenter is the presenter of the view where the Listener is written. In the view "GameView" I have implemented the Array of textfields. But now when I run the given solution I go the following NullPointerException:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at be.kdg.letterpyramide.view.GameView.GamePresenter.addTextLimiter(GamePresenter.java:36)
at be.kdg.letterpyramide.view.GameView.GameView.lambda$layoutNodes$0(GameView.java:52)
at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)

GameView line: 36

 tf.textProperty().addListener(new ChangeListener<String>() {

GameView line: 52

Arrays.asList(tfLetters).forEach(tfLetters -> GamePresenter.addTextLimiter(tfLetters,1));

Sidenote: I made it public static so I can use it in my GamePresenter. I'm very new to Java.

Thanks in advance!

CaptainAaargh
  • 35
  • 1
  • 7

1 Answers1

2

This is a solution without a GridPane, but this is an easy process of adding the Fields also to a GridPane. And now with a TextFormatter that is much better.

import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ChangeListenerDemo extends Application {

  @Override
  public void start(Stage primaryStage) {
    List<TextField> fields = createLimitedTextFields(9, 1);

    VBox box = new VBox();
    box.getChildren().addAll(fields);

    Scene scene = new Scene(box, 300, 250);
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  private List<TextField> createLimitedTextFields(int num, int maxLength) {
    final List<TextField> fields = new ArrayList<>();

    final UnaryOperator<TextFormatter.Change> filter
            = (TextFormatter.Change change) -> {
       if (change.getControlNewText().length() > maxLength) {
             return null;
       }
       return change;
    };
    for (int i = 0; i < num; i++) {
      final TextField tf = new TextField();
      tf.setTextFormatter(new TextFormatter(filter));
      fields.add(tf);
    }
    return fields;
  }

  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    launch(args);
  }
}
aw-think
  • 4,723
  • 2
  • 21
  • 42
  • 1
    Using a `TextFormatter` is usually a preferable approach (for 8u60 and up), as it can catch the change before any bound properties are changed (before it's even applied to the `TextField`'s `text` Property, actually) – Itai Jul 15 '16 at 11:22
  • Nice, but... are you sure `getCaretPosition` is accurate? What if I paste something in the middle? (Max length is 10, text is "123456789" and I paste "abcdef" after the "1". Caret position would be <10, but total length would be >10). I think using `getControlNewText().length()` is the safest, and probably most straight-forward. – Itai Jul 15 '16 at 11:56
  • @sillyfly ...for 8u60 and up? [`TextFormatter.Change`](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TextFormatter.Change.html) is valid since 8u40. But this is a type of bean counting... – aw-think Jul 15 '16 at 12:39
  • True, I got the versions confused :D It's true that most people always use the latest update of a major version, but whenever a feature was added after the initial major release I find it better to specify, in case for some reason you are locked into an older version, or maybe just forgot to update... – Itai Jul 15 '16 at 12:51
  • @sillyfly Yes, i've started with JavaFX since 7u45, so I was a bit out of date :-) – aw-think Jul 15 '16 at 12:55