13

It may seem the question is the duplicate of this. But my question is i have developed a integer textfield in JavaFX by two ways. The code is given below

public class FXNDigitsField extends TextField
{
private long m_digit;
public FXNDigitsField()
{
    super();
}
public FXNDigitsField(long number)
{
    super();
    this.m_digit = number;
    onInitialization();
}

private void onInitialization()
{
    setText(Long.toString(this.m_digit));
}

@Override
public void replaceText(int startIndex, int endIndex, String text)
{
    if (text.matches(Constants.DIGITS_PATTERN) || text.equals(Constants.EMPTY_STRING)) {
        super.replaceText(startIndex, endIndex, text);
    }
}

@Override
public void replaceSelection(String text)
{
    if (text.matches(Constants.DIGITS_PATTERN) || text.equals(Constants.EMPTY_STRING)) {
        super.replaceSelection(text);
    }
}
}

And the second way is by adding an event Filter.

The code snippet is given.

 // restrict key input to numerals.
this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
  @Override public void handle(KeyEvent keyEvent) {
    if (!"0123456789".contains(keyEvent.getCharacter())) {
      keyEvent.consume();
    }
  }
});

My question is which is the slandered way to do this? Can anyone help me to pick up the right?

asio_guy
  • 3,667
  • 2
  • 19
  • 35
Nikhil
  • 2,857
  • 9
  • 34
  • 58

5 Answers5

12

The best way to add validation in TextField is a 3rd way. This method lets TextField finish all processing (copy/paste/undo safe). It does not require you to extend the TextField class. And it allows you to decide what to do with new text after every change (to push it to logic, or turn back to previous value, or even to modify it).

// fired by every text property changes
textField.textProperty().addListener(
  (observable, oldValue, newValue) -> {
    // Your validation rules, anything you like
    // (! note 1 !) make sure that empty string (newValue.equals("")) 
    //   or initial text is always valid
    //   to prevent inifinity cycle
    // do whatever you want with newValue

    // If newValue is not valid for your rules
    ((StringProperty)observable).setValue(oldValue);
    // (! note 2 !) do not bind textProperty (textProperty().bind(someProperty))
    //   to anything in your code.  TextProperty implementation
    //   of StringProperty in TextFieldControl
    //   will throw RuntimeException in this case on setValue(string) call.
    //   Or catch and handle this exception.

    // If you want to change something in text
    // When it is valid for you with some changes that can be automated.
    // For example change it to upper case
    ((StringProperty)observable).setValue(newValue.toUpperCase());
  }
);
Damien
  • 1,161
  • 2
  • 19
  • 31
gmatagmis
  • 318
  • 2
  • 5
  • 1
    This approach has some downsides (additional to not being able to bind the textProperty). For a short amout of time the invalid value will still be present in the textProperty. If you have a listener or data-binding attached to the textProperty, they will get invalid values. A better approach is to use the `TextFormatter` class from JavaFX. – Manuel Mauky Apr 18 '18 at 17:18
6

JavaFX has a class TextFormatter for this use-case. It allows you to validate and adjust the text content before it is "commited" to the textProperty of the TextField.

See this example:

TextFormatter<String> textFormatter = new TextFormatter<>(change -> {
    if (!change.isContentChange()) {
        return change;
    }

    String text = change.getControlNewText();

    if (isValid(text)) { // your validation logic
        return null;
    }


    return change;
});

textField.setTextFormatter(textFormatter);
Manuel Mauky
  • 2,116
  • 4
  • 21
  • 25
5

In both of your ways, you are not allowed to type characters other then numeric characters. But it will allow to paste any character there (Copy Text from any source and Paste in your TextField).

A good way to do validation is after submitting it,

Like (For integers):

try {
    Integer.parseInt(myNumField.getText());
} catch(Exception e) {
    System.out.println("Non-numeric character exist");
}

(or you can use any combination of yours + the above method)

demongolem
  • 9,474
  • 36
  • 90
  • 105
Shreyas Dave
  • 3,815
  • 3
  • 28
  • 57
0

Similar of what Manuel Mauky posted, but in groovy is:

Note: This will prevent any other character except for digits to be input.

def digitsOnlyOperator = new UnaryOperator<TextFormatter.Change>() {
        @Override
        TextFormatter.Change apply(TextFormatter.Change change) {
            return !change.contentChange || change.controlNewText.isNumber() ? change : null
        }
}
textField.textFormatter = new TextFormatter<>(digitsOnlyOperator)

There is probably a shorter way to do it in groovy. If you know it, please post it here.

lepe
  • 24,677
  • 9
  • 99
  • 108
  • 1
    don't know grovy but pretty sure that it has access to locale-aware NumberFormatters as well ;) So no: never-ever do any manual pattern matching for Numbers, let the specilized classes handle it and then use in the TextFormatter (darn, how comes that lately all the old incorrect code snippets are sprouting all over the place?) – kleopatra Jan 11 '19 at 11:47
  • @kleopatra : You are right... I don't know why I missed that! I updated my answer to use isNumber instead. In case someone need to extend it and support dots or other special format, then regex is easier IMHO. – lepe Jan 12 '19 at 07:28
  • better but still not good: let a NumberFormatter decide if the given change represents a valid Number :) – kleopatra Jan 12 '19 at 10:33
0

This code snippet allows just digits to be entered by the user in a TextField.

    /**
     * This will check whether the incoming data from the user is valid.
     */
    UnaryOperator<TextFormatter.Change> numberValidationFormatter = change -> {
        if (change.getText().matches("\\d+")) {
            return change; //if change is a number
        } else {
            change.setText(""); //else make no change
            change.setRange( //don't remove any selected text either.
                    change.getRangeStart(),
                    change.getRangeStart()
            );
            return change;
        }
    };
    
     
    TextFormatter tf = new TextFormatter(numberValidationFormatter);
    
    textfield.setTextFormatter(tf);
Mumbod
  • 1
  • 1