'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 1:[1]

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;
}

Solution 2:[2]

I'm using this simple class until such time as a TimePicker corresponding to the DatePicker becomes available. The strategy here is to check the entry when the field loses focus. If it doesn't match the regular expression (with optional seconds and optional AM, PM, am, pm), it draws user attention by beeping, replacing the entry with a well formatted one, requesting focus, and highlighting the entry. I make validate() public so that a container (e.g., a Dialog) can also check on validity before closing (e.g., in case focus was lost by pressing an OK button):

public class TimeTextField extends TextField 
{   
    public TimeTextField(String init) {
        super(init) ;
        focusedProperty().addListener((o, oldV, newV) -> changed(newV));
    }

    public TimeTextField() {
        this("12:00 PM");
    }

    private void changed(boolean focus) {
        if (!focus) {
            if (!validate()) { 
                setText("12:00 PM");
                selectAll();
                requestFocus();
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }

    public boolean validate()  {
        return getText()
        .matches("(0?[1-9]|1[0-2]):[0-5][0-9](:[0-5][0-9])? ?[APap][mM]$");
    }    
}

Solution 3:[3]

You should insert ':' by yourself. Because in this way the user is Only allowed to insert numbers. After each two numbers you insert the ':' inside the change listener. Additional it is easy to insert the time.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Paul
Solution 3 kayf