Montag, 13. Mai 2013

Restricting user input on a TextField

Have you ever came to the problem to limit the input in a text field to a maximal length?

Or to restrict the input to specific characters, like allowing only numbers?

Of course there are already some solutions around, but most of them suffer from the problem, that they still allow invalid user input by pasting invalid characters with Ctrl + V or via the context menu, because they only check key events or use some other technique like overriding replaceText.

Here's a simple class that I want to share with you, which let you restrict the input to a regular expression class and also let you set a maximal length, just like in HTML, no matter, if the user pasted or typed it into the text field.

Basically it just listens to text changes and if it is too long or does not match it is either truncated or the old text is set instead.

By the way: the property names (maxLength, restrict) are burrowed from HTML and Adobe Flex respectively.
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextField;

/**
 * A text field, which restricts the user's input.
 * <p>
 * The restriction can either be a maximal number of characters which the user is allowed to input
 * or a regular expression class, which contains allowed characters.
 * </p>
 * <p/>
 * <b>Sample, which restricts the input to maximal 10 numeric characters</b>:
 * <pre>
 * {@code
 * RestrictiveTextField textField = new RestrictiveTextField();
 * textField.setMaxLength(10);
 * textField.setRestrict("[0-9]");
 * }
 * </pre>
 *
 * @author Christian Schudt
 */
public class RestrictiveTextField extends TextField {

    private IntegerProperty maxLength = new SimpleIntegerProperty(this, "maxLength", -1);
    private StringProperty restrict = new SimpleStringProperty(this, "restrict");

    public RestrictiveTextField() {

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

            private boolean ignore;

            @Override
            public void changed(ObservableValue<? extends String> observableValue, String s, String s1) {
                if (ignore || s1 == null)
                    return;
                if (maxLength.get() > -1 && s1.length() > maxLength.get()) {
                    ignore = true;
                    setText(s1.substring(0, maxLength.get()));
                    ignore = false;
                }

                if (restrict.get() != null && !restrict.get().equals("") && !s1.matches(restrict.get() + "*")) {
                    ignore = true;
                    setText(s);
                    ignore = false;
                }
            }
        });
    }

    /**
     * The max length property.
     *
     * @return The max length property.
     */
    public IntegerProperty maxLengthProperty() {
        return maxLength;
    }

    /**
     * Gets the max length of the text field.
     *
     * @return The max length.
     */
    public int getMaxLength() {
        return maxLength.get();
    }

    /**
     * Sets the max length of the text field.
     *
     * @param maxLength The max length.
     */
    public void setMaxLength(int maxLength) {
        this.maxLength.set(maxLength);
    }

    /**
     * The restrict property.
     *
     * @return The restrict property.
     */
    public StringProperty restrictProperty() {
        return restrict;
    }

    /**
     * Gets a regular expression character class which restricts the user input.<br/>
     *
     * @return The regular expression.
     * @see #getRestrict()
     */
    public String getRestrict() {
        return restrict.get();
    }

    /**
     * Sets a regular expression character class which restricts the user input.<br/>
     * E.g. [0-9] only allows numeric values.
     *
     * @param restrict The regular expression.
     */
    public void setRestrict(String restrict) {
        this.restrict.set(restrict);
    }
}

Kommentare:

  1. Dieser Kommentar wurde vom Autor entfernt.

    AntwortenLöschen
    Antworten
    1. Thanks for the class Christian. I have found it really handy. The need for good validation cannot be overstated.

      Löschen
    2. Hi Christian,
      Can you tell me how to create the class in FXML add it to Scene Builder ? I want this type of data in several fields, in my user input. Once I insert this class in (say NumberField) in Scene Builder, I would be able to use it wherever I want. Thanks in Advance.
      Hornigold

      Löschen
    3. Hi Arthur, sorry, but I've never worked with Scene Builder, so I can't help. But the class is fairly simple, I can't imagine it's a big problem.

      Löschen
  2. Dieser Kommentar wurde vom Autor entfernt.

    AntwortenLöschen
    Antworten
    1. Hi, is there a way to use your class with text fields? I'm asking cause I have all my forms on fxml and I would like to use your class, is there a way to achive this?

      Thanks

      Löschen
    2. You probably have to replace all your TextField instances by RestrictedTextField instances.

      Löschen
    3. hi Chris
      I create new TextField by FXML.
      Then i create it by TextField textField = new RestrictedTextField(). Can it work?
      I'm using it but it do nothing :(

      Löschen
    4. Maybe try (for example):
      RestrictedTextField textField = new RestrictedTextField();
      textField.setMaxLength(4);

      You have, of course, specify some value (maxlength, regex), if you want it to do something.

      Löschen
  3. Fyi since Java 8u40 there's a TextFormatter which allows you to filter the text input.

    AntwortenLöschen
  4. java 8_40 once you reference your textfield

    @FXML private TextField txtInt;
    txtInt.textFormatterProperty().setValue(new TextFormatter(new IntegerStringConverter()));

    AntwortenLöschen