Search code examples
regexjavafxtextfieldtext-formatting

How to format a text field javafx


I would like to be able to enter the time in a javafx text field in the format hh:mm:ss. How would I go about formatting the text field? I already got the regex method so it only accepts numbers as inputs:

public void format(TextField t, String regex){
    TextFormatter<String> formatter = new TextFormatter<String>( change -> {
        change.setText(change.getText().replaceAll(regex, ""));
        return change; 


    });
    t.setTextFormatter(formatter);
}

and I also got a method that limits the amount of characters allowed in the textfield:

public void limitLength(int maxLength, TextField t){
    t.lengthProperty().addListener(new ChangeListener<Number>() {

        @Override
        public void changed(ObservableValue<? extends Number> observable,
                Number oldValue, Number newValue) {
            if (newValue.intValue() > oldValue.intValue()) {
                // Check if the new character is greater than LIMIT
                if (t.getText().length() >= maxLength) {

                    t.setText(t.getText().substring(0, maxLength));
                }
            }
        }
    });
}

However none of this helps me get the time format I want. The ideal would be the initial text being 00:00:00 and the user ONLY being able to edit the digits, unable to touch the colons. It would also be awesome if I could be able to make sure that between each colon there are 2 digits, ie the user should be unable to move around the digits, like this 0:000:00.

Thanks for any help.


Solution

  • Using SimpleDateFormat

    There is already a class that is designed for formatting dates that you can use with the TextField's textFormatter: SimpleDateFormat

    TextField tf = new TextField();
    SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
    tf.setTextFormatter(new TextFormatter<>(new DateTimeStringConverter(format), format.parse("00:00:00")));
    

    See javadoc for SimpleDateFormat for a more detailed decription of the meaning of the characters.

    Using 3 different TextFields

    You could also use 3 different TextFields and remove background and border and put them in a HBox to get non-editable :s:

    TextField hours = new TextField();
    TextField minutes = new TextField();
    TextField seconds = new TextField();
    StringConverter<Integer> minSecConverter = new IntRangeStringConverter(0, 59);
    minutes.setTextFormatter(new TextFormatter<>(minSecConverter, 0));
    seconds.setTextFormatter(new TextFormatter<>(minSecConverter, 0));
    hours.setTextFormatter(new TextFormatter<>(new IntRangeStringConverter(0, 23), 0));
    prepareTextField(hours);
    prepareTextField(minutes);
    prepareTextField(seconds);
        
    HBox fields = new HBox(hours, createLabel(),minutes, createLabel(), seconds);
    fields.setPadding(new Insets(4));
    fields.setStyle("-fx-background-color: white;");
    
    public static void prepareTextField(TextField tf) {
        tf.setAlignment(Pos.CENTER);
        tf.setBackground(Background.EMPTY);
        tf.setBorder(Border.EMPTY);
        tf.setPadding(Insets.EMPTY);
        tf.setPrefColumnCount(2);
    }
    
    public static class IntRangeStringConverter extends StringConverter<Integer> {
    
        private final int min;
        private final int max;
    
        public IntRangeStringConverter(int min, int max) {
            this.min = min;
            this.max = max;
        }
        
        @Override
        public String toString(Integer object) {
            return String.format("%02d", object);
        }
    
        @Override
        public Integer fromString(String string) {
            int integer = Integer.parseInt(string);
            if (integer > max || integer < min) {
                throw new IllegalArgumentException();
            }
    
            return integer;
        }
    
    }
    
    public static Label createLabel() {
        Label label = new Label(":");
        label.setPrefWidth(3);
        return label;
    }