使用 TextFormatter 时无法使用 DatePicker 选择框

Unable to use DatePicker choose box when using TextFormatter

我想创建三 (3) 组组合框(年、月、日)。 Combobox Day 应该只在组合框月份和年份正确归档之前启用,并且值应该根据给定的月份和年份同步。 (这意味着它应该检查闰年)。

这是我目前所知道的,我有一个提示,我应该使用绑定 and/or 听众来做到这一点,但很难做到。

public class Testing extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        final JFXComboBox<Month> cbMonths = new JFXComboBox<>();
        final JFXComboBox<Integer> cbYears = new JFXComboBox<>();
        final JFXComboBox<Integer> cbDays = new JFXComboBox<>();

        // Month Values
        cbMonths.getItems().setAll(Month.values());
        // Year Values
        Calendar calendar = Calendar.getInstance();
        for (int i = calendar.get(Calendar.YEAR) ;
             i >= (calendar.get(Calendar.YEAR) -35) ; i--)
        {
            cbYears.getItems().add(i);
        }

        // NOTE: will cause NPE
        //       I want to insert this code only when cbMonth and cbYears has a value
        YearMonth numberOfDays = YearMonth.of(cbYear.getValue(), cbMonth.getValue());
        for (int i = 1 ; i >= numberOfDays.lengthOfMonth() ; i ++) {
            cbDays.getItems().add(i);
        }

        final HBox root = new HBox(cbMonth, cbYear, cbDays);
        root.setAlignment(Pos.CENTER);
        root.setSpacing(10.0);
        root.setPadding(new Insets(10, 10, 10, 10));
        Scene scene = new Scene(root, 300, 200);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

编辑

由于时间不够,我尝试了其他选择。

选项 1:

正如@Zephyr 指出的那样,我切换到日期选择器并将其设置为 editable。我试图覆盖它的一些默认设置来得到我想要的输出。但我注意到,每当我使用 TextFormatter 时,我都无法在 DatePicker 选择框中选择日期。这是示例代码

public class DatePickerFinal extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        final String DATE_REGEX = "(0[1-9]|1[012])\s(0[1-9]|[12][0-9]|3[01])\s((19|2[0-9])[0-9]{2})";
        final DateTimeFormatter SHOW_DATE = DateTimeFormatter.ofPattern("MMMM dd, yyyy", Locale.getDefault());
        final DateTimeFormatter ENTER_DATE = DateTimeFormatter.ofPattern("MM dd yyyy", Locale.getDefault());
        final LocalDate TODAY = LocalDate.now();

        final JFXDatePicker DATE_PICKER = new JFXDatePicker();

        // Disable some dates
        DATE_PICKER.setDayCellFactory(new Callback<DatePicker, DateCell>() {
            @Override
            public DateCell call(DatePicker datePicker) {
                return new DateCell() {
                    @Override
                    public void updateItem(LocalDate localDate, boolean b) {
                        super.updateItem(localDate, b);
                        setDisable(b || localDate.compareTo(TODAY) > 0 || localDate.compareTo(TODAY.minusYears(45)) < 0);
                    }
                };
            }
        });

        // Add StringConverter to make it more readable,
        // and also rejecting disable dates inputted by the user
        DATE_PICKER.setConverter(new StringConverter<LocalDate>() {
            @Override
            public String toString(LocalDate localDate) {
                if (localDate == null) {
                    return "";
                } else if (localDate.isAfter(TODAY) || localDate.isBefore(TODAY.minusYears(45))) {
                    return "";
                } else {
                    return SHOW_DATE.format(localDate);
                }
            }

            @Override
            public LocalDate fromString(String s) {
                return (s == null || s.isEmpty()) ? null : LocalDate.parse(s, ENTER_DATE);
            }
        });


        // Then I want to manage user input so that they can only enter digits to the date picker
        // then format it accordingly.

        DATE_PICKER.getEditor().setTextFormatter(new TextFormatter<Object>(change -> {
            String enteredText = change.getText();
            if((enteredText.matches("[\d]+")) || change.isDeleted()) {
                final int oldTextLength = change.getControlText().length();
                int newTextLength = change.getControlNewText().length();

                if (newTextLength < oldTextLength) return change;
                switch (newTextLength) {
                    case 2 :
                    case 5 :
                        StringBuilder stringBuilder = new StringBuilder(enteredText);
                        stringBuilder.append(" ");
                        change.setText(stringBuilder.toString());
                        newTextLength++;
                        break;
                    case 11 :
                        return null;
                }
                change.setCaretPosition(newTextLength);
                change.setAnchor(newTextLength);
                return change;
            }
            return null;
        }));

        
        // Add some validators where if the user input was valid or not. The below code was still in progress though.
        RequiredFieldValidator requiredFieldValidator = new RequiredFieldValidator();
        requiredFieldValidator.setMessage("Field Should Not Be Empty");
        RegexValidator regexValidator = new RegexValidator("MM DD YYYY");
        regexValidator.setRegexPattern(DATE_REGEX);
        DATE_PICKER.setValidators(regexValidator);
        DATE_PICKER.focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean t1) {
                if (t1) {
                    DATE_PICKER.validate();
                }
            }
        });


        DATE_PICKER.getEditor().textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observableValue, String s, String t1) {
                if (!DATE_PICKER.getEditor().getText().matches(DATE_REGEX)) {
                    DATE_PICKER.validate();
                }
            }
        });


        VBox root = new VBox(20, DATE_PICKER, new JFXButton("Button"));
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 300, 120);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

除了可编辑之外,我还希望用户只需单击 and/or 从选择框中选择日期。我希望有人能给我指出正确的方向:)

在@kleopatra 的帮助下。我的方案是创建一个class负责解析用户在DatePickers默认选择框上选择的日期。此外,日期选择器设置为可编辑,以便用户也可以手动编辑它。但是,有一个限制,用户可以 ONLY 在手动编辑时插入数值,我也想确保用户只输入有效的日期。


MCVE

public class DatePickerFinal extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        final String DATE_OF_BIRTH_REGEX
                = "(0[1-9]|1[012])\s(0[1-9]|[12][0-9]|3[01])\s((19|2[0-9])[0-9]{2})";
        final DateTimeFormatter showingDateFormat = DateTimeFormatter.ofPattern("MMMM dd, yyyy", Locale.getDefault());
        final DateTimeFormatter inputtedDateFormat = DateTimeFormatter.ofPattern("MM dd yyyy", Locale.getDefault());
        final LocalDate dateToday = LocalDate.now();

        final JFXDatePicker datePicker = new JFXDatePicker();

        // Disable some dates
        datePicker.setDayCellFactory(new Callback<DatePicker, DateCell>() {
            @Override
            public DateCell call(DatePicker datePicker) {
                return new DateCell() {
                    @Override
                    public void updateItem(LocalDate localDate, boolean b) {
                        super.updateItem(localDate, b);
                        setDisable(b || localDate.compareTo(dateToday) > 0 || localDate.compareTo(dateToday.minusYears(45)) < 0);
                    }
                };
            }
        });

        // Add StringConverter to make it more readable,
        // and also rejecting disable dates inputted by the user
        datePicker.setConverter(new StringConverter<LocalDate>() {
            @Override
            public String toString(LocalDate localDate) {
                if (localDate == null) {
                    return "";
                } else if (localDate.isAfter(dateToday) || localDate.isBefore(dateToday.minusYears(45))) {
                    return "";
                } else {
                    return showingDateFormat.format(localDate);
                }
            }

            @Override
            public LocalDate fromString(String s) {
                return (s == null || s.isEmpty()) ? null : LocalDate.parse(s, inputtedDateFormat);
            }
        });

        // Add a validator
        RequiredFieldValidator requiredFieldValidator = new RequiredFieldValidator();
        requiredFieldValidator.setMessage("Enter with the format\nMM DD YYYY");
        datePicker.setValidators(requiredFieldValidator);

        // Format the user's input field
        datePicker.getEditor().setTextFormatter(new TextFormatter<>(change -> {
            String textEntered = change.getText();

            DateValidator validator;

            if (change.isContentChange()) {

                validator = new DateValidator(change.getControlNewText(), showingDateFormat);
                if (!validator.isValid()) {
                    datePicker.validate();
                } else {
                    datePicker.resetValidation();
                    return change;
                }

                if (textEntered.matches("\D+")) {
                    return null;
                } else {
                    final int oldLength = change.getControlText().length();
                    int newLength = change.getControlNewText().length();
                    if (newLength < oldLength) return change;

                    if (newLength == 2 || newLength == 5) {
                        change.setText(textEntered + " ");
                        newLength++;
                    } else if (newLength == 11) {
                        validator = new DateValidator(change.getControlNewText(), inputtedDateFormat);
                        if (!validator.isValid()) {
                            return null;
                        } else {
                            datePicker.resetValidation();
                        }
                    }

                    change.setCaretPosition(newLength);
                    change.setAnchor(newLength);
                }
            }

            return change;
        }));

        datePicker.focusedProperty().addListener((observableValue, wasFocused, isFocused) -> {
            if (isFocused) {
                Platform.runLater(()-> {
                    datePicker.validate();
                    datePicker.getEditor().selectAll();
                });
            } else {
                datePicker.resetValidation();
            }
        });

        datePicker.getEditor().textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observableValue, String s, String t1) {
                if (t1.matches(DATE_OF_BIRTH_REGEX)) {
                    datePicker.resetValidation();
                }
            }
        });

        // Show picker choice box on MouseEvent
        datePicker.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseEvent -> {
            datePicker.show();
        });

        VBox root = new VBox(50, datePicker, new JFXButton("Button"));
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 300, 120);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private static class DateValidator  {
        DateTimeFormatter formatter;
        String date;

        DateValidator (String date, DateTimeFormatter formatter) {
            this.date = date;
            this.formatter = formatter;
        }

        public boolean isValid() {
            try {
                LocalDate.parse(this.date, this.formatter);
            } catch (Exception e) {
                return false;
            }
            return true;
        }
    }
}