Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX DatePicker with better typing features

Tags:

java

javafx

Let's suppose we have a DatePicker without a date set. So if the user wants to type a date, he need to type the entire date, including the slashes: 10/11/2014

It would be good if as the user types the numbers, the slashes could be automatically included (typing 10112014 would set the date correctly).

Another thing is, if there is already a date set on the date picker (10/11/2014), and the user wants to type another date (01/11/2014), then he puts the cursor on the beginning of the text and types the date. The result would be something like 01/11/201410/11/2014. It would be better if when typing on the field of the date picker, it went automatically into insert mode, so as the user types the date it overwrites the original date.

Is there a way to do these things?

--- UPDATE---

Thanks to José Pereda, i found the solution. I got his code and changed a bit:

public static void enhanceDatePickers(DatePicker... datePickers) {
for (DatePicker datePicker : datePickers) {
    datePicker.setConverter(new StringConverter<LocalDate>() {

        private final DateTimeFormatter fastFormatter1 = DateTimeFormatter.ofPattern("ddMMuuuu");
        private final DateTimeFormatter fastFormatter2 = DateTimeFormatter.ofPattern("d/M/u");
        private final DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");

        @Override
        public String toString(LocalDate object) {
            return object.format(defaultFormatter);
        }

        @Override
        public LocalDate fromString(String string) {
            try{ return LocalDate.parse(string, fastFormatter1); } catch(DateTimeParseException ignored){}
            try{ return LocalDate.parse(string, fastFormatter2); } catch(DateTimeParseException ignored){}
            return LocalDate.parse(string, defaultFormatter);
        }
    });

    TextField textField = datePicker.getEditor();
    textField.addEventHandler(KeyEvent.KEY_TYPED, event -> {
        if (!"0123456789/".contains(event.getCharacter())) {
            return;
        }
        if ("/".equals(event.getCharacter()) && (textField.getText().isEmpty() || textField.getText().charAt(textField.getCaretPosition()-1)=='/')) {
            //If the users types slash again after it has been added, cancels it.
            System.out.println("Cancelando o bagulho!");
            event.consume();
        }
        textField.selectForward();
        if (!event.getCharacter().equals("/") && textField.getSelectedText().equals("/")) {
            textField.cut();
            textField.selectForward();
        }
        textField.cut();

        Platform.runLater(() -> {
            String textUntilHere = textField.getText(0, textField.getCaretPosition());
            if (textUntilHere.matches("\\d\\d") || textUntilHere.matches("\\d\\d/\\d\\d")) {
                String textAfterHere = "";
                try { textAfterHere = textField.getText(textField.getCaretPosition()+1, textField.getText().length()); } catch (Exception ignored) {}
                int caretPosition = textField.getCaretPosition();
                textField.setText(textUntilHere + "/" + textAfterHere);
                textField.positionCaret(caretPosition+1);
            }
        });
    });
}

} Then, to "enhance" the DatePicker i just call this method passing all my desired datePicker instances as parameters

like image 478
Mateus Viccari Avatar asked Dec 18 '14 12:12

Mateus Viccari


1 Answers

Actually, you can do both requests.

The first part is easy, since you can use more than one date converter. Let's say you have a default format "dd/MM/uuuu" and a fast format "ddMMuuuu":

private final DateTimeFormatter fastFormatter = DateTimeFormatter.ofPattern("ddMMuuuu");
private final DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");

Then you have to provide your custom converter, keeping the default format for toString() method and modifying fromString() so you can type with any of your formatters:

DatePicker datePicker=new DatePicker();
datePicker.setValue(LocalDate.now());
datePicker.setConverter(new StringConverter<LocalDate>() {

    @Override
    public String toString(LocalDate object) {
        return object.format(defaultFormatter);
    }

    @Override
    public LocalDate fromString(String string) {
        try{
            return LocalDate.parse(string, fastFormatter);
        } catch(DateTimeParseException dtp){}

        return LocalDate.parse(string, defaultFormatter);
    }
});

For the second request (keeping in mind you can type with or without slashes), you can simulate an insert mode by deleting the next character on the editor after you type one valid character.

Assuming you always type the slashes this will do:

datePicker.getEditor().setOnKeyTyped(event -> {
    if (!"0123456789/".contains(event.getCharacter())) {
        return;
    }
    datePicker.getEditor().selectForward();
    datePicker.getEditor().cut();
});

But if you don't type them you need to delete not only the next digit but also the next slash on the editor:

datePicker.getEditor().setOnKeyTyped(event -> {
    if (!"0123456789/".contains(event.getCharacter())) {
        return;
    }
    datePicker.getEditor().selectForward();
    if(!event.getCharacter().equals("/") && 
       datePicker.getEditor().getSelectedText().equals("/")){
        datePicker.getEditor().cut();
        datePicker.getEditor().selectForward();
    }
    datePicker.getEditor().cut();
});

Note this will be valid either if you use slashes or not. Also notice you will have to type the 0 leading digit when necessary.

like image 191
José Pereda Avatar answered Oct 01 '22 13:10

José Pereda