The converter is different to the filter: the converter specifies how to convert the text to a value, and the filter filters changes the user may make. It sounds like here you want both, but you want the filter to more accurately filter the changes that are allowed.
I usually find it easiest to check the new value of the text if the change were accepted. You want to optionally have a -, followed by 1-9 with any number of digits after it. It's important to allow an empty string, else the user won't be able to delete everything.
So you probably need something like
UnaryOperator<Change> integerFilter = change -> { String newText = change.getControlNewText(); if (newText.matches("-?([1-9][0-9]*)?")) { return change; } return null; }; myNumericField.setTextFormatter( new TextFormatter<Integer>(new IntegerStringConverter(), 0, integerFilter));
You can even add more functionality to the filter to let it process - in a smarter way, e.g.
UnaryOperator<Change> integerFilter = change -> { String newText = change.getControlNewText(); // if proposed change results in a valid value, return change as-is: if (newText.matches("-?([1-9][0-9]*)?")) { return change; } else if ("-".equals(change.getText()) ) { // if user types or pastes a "-" in middle of current text, // toggle sign of value: if (change.getControlText().startsWith("-")) { // if we currently start with a "-", remove first character: change.setText(""); change.setRange(0, 1); // since we're deleting a character instead of adding one, // the caret position needs to move back one, instead of // moving forward one, so we modify the proposed change to // move the caret two places earlier than the proposed change: change.setCaretPosition(change.getCaretPosition()-2); change.setAnchor(change.getAnchor()-2); } else { // otherwise just insert at the beginning of the text: change.setRange(0, 0); } return change ; } // invalid change, veto it by returning null: return null; };
This will let the user press - at any point and it will toggle the sign of the integer.
SSCCE:
import java.util.function.UnaryOperator; import javafx.application.Application; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.control.TextFormatter; import javafx.scene.control.TextFormatter.Change; import javafx.scene.layout.VBox; import javafx.stage.Stage; import javafx.util.StringConverter; import javafx.util.converter.IntegerStringConverter; public class IntegerFieldExample extends Application { @Override public void start(Stage primaryStage) { TextField integerField = new TextField(); UnaryOperator<Change> integerFilter = change -> { String newText = change.getControlNewText(); if (newText.matches("-?([1-9][0-9]*)?")) { return change; } else if ("-".equals(change.getText()) ) { if (change.getControlText().startsWith("-")) { change.setText(""); change.setRange(0, 1); change.setCaretPosition(change.getCaretPosition()-2); change.setAnchor(change.getAnchor()-2); return change ; } else { change.setRange(0, 0); return change ; } } return null; }; // modified version of standard converter that evaluates an empty string // as zero instead of null: StringConverter<Integer> converter = new IntegerStringConverter() { @Override public Integer fromString(String s) { if (s.isEmpty()) return 0 ; return super.fromString(s); } }; TextFormatter<Integer> textFormatter = new TextFormatter<Integer>(converter, 0, integerFilter); integerField.setTextFormatter(textFormatter); // demo listener: textFormatter.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue)); VBox root = new VBox(5, integerField, new Button("Click Me")); root.setAlignment(Pos.CENTER); Scene scene = new Scene(root, 300, 120); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }
Tutorial
A comprehensive tutorial guide on using a TextFormatter: