Search code examples
javajavafxiptextfieldipv4

javafx textfield ip address


I would like to make sure the user enter a valid IPv4 address in a JavaFX TextField (0.0.0.0 to 255.255.255.255) but i can't seems to find any valid solutions.

I tried with that code :

    private final TextField serverURI = new TextField();
    final UnaryOperator<TextFormatter.Change> urlFilter = new UnaryOperator<TextFormatter.Change>() {
        @Override
        public TextFormatter.Change apply(TextFormatter.Change change) {
            final String text = change.getText();
            return (text.isEmpty() || text.matches("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")) ? change : null;
        }
    };
    final TextFormatter<String> urlFormatter = new TextFormatter(urlFilter);
    serverURI.setTextFormatter(urlFormatter);

But I can't even type one digit... It seems to work with only one input


Solution

  • These are tricky.

    The first note is that you are using change.getText(), which gives the text being added or removed; you want to test the resulting text (i.e. the text after adding or deleting). For this, use change.getControlNewText().

    For example, if the current text in the text field is "255.2", and the user types a "5", then change.getText() will return "5", whereas change.getControlNewText() will return "255.25".

    This still leaves the main issue, which is that the filter is being applied to every individual change to the text, and you are testing for a complete ip address. So, for example, to type "255.255.255.255", the user would first type "2". You test (even with change.getControlNewText()) if "2" matches your regular expression, it fails (because it's not a complete IP address), and so the change is vetoed. On the next key typed, change.getControlNewText() will be "25", and so on. So you want the entire sequence "2", "25", "255", "255.", "255.2", etc, etc to pass your filter. You also need the filter to accept the user deleting characters, copying and pasting, and so on.

    So you really need a regex that tests for partial entries, instead of complete entries, which is of course a little trickier to get right. The following is an example, not intended to be bullet-proof, but should get you on the right track (at least). Note that you probably want additional validation when the user commits the value, to check you have a complete, valid ip address.

    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.control.TextFormatter.Change;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;
    
    public class IPTextFieldTest extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            TextField ipTextField = new TextField();
            String regex = makePartialIPRegex();
            final UnaryOperator<Change> ipAddressFilter = c -> {
                String text = c.getControlNewText();
                if  (text.matches(regex)) {
                    return c ;
                } else {
                    return null ;
                }
            };
            ipTextField.setTextFormatter(new TextFormatter<>(ipAddressFilter));
            StackPane root = new StackPane(ipTextField);
            Scene scene = new Scene(root, 350, 120);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private String makePartialIPRegex() {
            String partialBlock = "(([01]?[0-9]{0,2})|(2[0-4][0-9])|(25[0-5]))" ;
            String subsequentPartialBlock = "(\\."+partialBlock+")" ;
            String ipAddress = partialBlock+"?"+subsequentPartialBlock+"{0,3}";
            return "^"+ipAddress ;
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    }