Use the current value of a DatePicker as the minimum/maximum value of another DatePicker

IggyBlob :

I have a JavaFX UI that allows the user to specify a time span. It consists of two DatePickers, one for selecting the start date and one for selecting the end date of the time span.

Obviously, I don't want the user to be able to select a start date that lies after the end date and vice-versa.

I've already set up a custom DayCellFactory for both date pickers, as described in https://stackoverflow.com/a/46372951/5801146. This works as intended for the initial time range specified during the initialisation of the DatePickers. However, if the user then selects, for instance, a different start date, the end date DatePicker still shows the initial selectable dates and doesn't adapt its selectable dates to the new start date. Here's my approach using property listeners, which - of course - does not work:

public final class DatePickerUtil {

    public static void constrainLowerUpperBounds(DatePicker lowerBoundDatePicker, DatePicker upperBoundDatePicker) {
        LocalDate lowerBound = lowerBoundDatePicker.getValue();
        LocalDate upperBound = upperBoundDatePicker.getValue();

        if (lowerBound.isAfter(upperBound)) {
            throw new IllegalStateException("Upper bound date picker has older date than lower bound date picker");
        }

        lowerBoundDatePicker.setEditable(true);
        upperBoundDatePicker.setEditable(true);

        lowerBoundDatePicker.setDayCellFactory(createDayCellFactory(null, upperBound));
        upperBoundDatePicker.setDayCellFactory(createDayCellFactory(lowerBound, null));

        lowerBoundDatePicker.valueProperty().addListener((observable, oldValue, newValue) -> {
            upperBoundDatePicker.setDayCellFactory(createDayCellFactory(newValue, null));
        });

        upperBoundDatePicker.valueProperty().addListener(((observable, oldValue, newValue) -> {
            lowerBoundDatePicker.setDayCellFactory(createDayCellFactory(null, newValue));
        }));
    }

    private static Callback<DatePicker, DateCell> createDayCellFactory(LocalDate lowerBound, LocalDate upperBound) {
        return picker -> new DateCell() {
            @Override
            public void updateItem(LocalDate date, boolean empty) {
                super.updateItem(date, empty);
                boolean isBeforeLowerBound = lowerBound != null && date.isBefore(lowerBound);
                boolean isAfterUpperBound = upperBound != null && date.isAfter(upperBound);
                Platform.runLater(() -> setDisable(empty || isBeforeLowerBound || isAfterUpperBound));
            }
        };
    }
}

What I want to achieve is some sort of binding like in WPF/UWP where you can bind the minimum value of the end date DatePicker to the current value of the start date DatePicker, and the maximum value of the start date DatePicker to the current value of the end date DatePicker. How can I achieve this behaviour in JavaFX?

Sunflame :

you can pass the first DatePicker's value to the second one and after it changes it updates the second one.

Here is a simple example:

public class MinMaxDate {

    private void test() {
        DatePicker firstDP = new DatePicker();
        DatePicker secondDP = new DatePicker();

        // set the min date
        secondDP.setDayCellFactory(cf -> new MinDateCell(firstDP.valueProperty()));
        // set the max date
        secondDP.setDayCellFactory(cf -> new MaxDateCell(firstDP.valueProperty()));
    }

    private class MinDateCell extends DateCell {

        private ObjectProperty<LocalDate> date;

        private MinDateCell(ObjectProperty<LocalDate> date) {
            this.date = date;
        }

        @Override
        public void updateItem(LocalDate item, boolean empty) {
            super.updateItem(item, empty);
            if (item.isBefore(date.get())) {
                this.setDisable(true);
                setStyle("-fx-background-color: #7e7e7e;"); // I used a different coloring to see which are disabled.
            }
        }

    }

    private class MaxDateCell extends DateCell {

        private ObjectProperty<LocalDate> date;

        private MaxDateCell(ObjectProperty<LocalDate> date) {
            this.date = date;
        }

        @Override
        public void updateItem(LocalDate item, boolean empty) {
            super.updateItem(item, empty);
            if (item.isAfter(date.get())) {
                this.setDisable(true);
                setStyle("-fx-background-color: #7e7e7e;"); // Same here
            }
        }

    }

}

I have created two different classes, but I am sure it can be solved using some fancy java8 function to make it much more general then you could use in that if any condition to disable a date.

Update

Here is the generic solution:

private class FilterDateCell extends DateCell {

        private ObjectProperty<LocalDate> date;
        private BiPredicate<LocalDate, LocalDate> filterPredicate;

        private FilterDateCell(
                ObjectProperty<LocalDate> date,
                BiPredicate<LocalDate, LocalDate> filterPredicate) {
            this.date = date;
            this.filterFunction = filterFunction;
        }

        @Override
        public void updateItem(LocalDate item, boolean empty) {
            super.updateItem(item, empty);
            if (filterPredicate.test(item, date.get())) {
                this.setDisable(true);
                setStyle("-fx-background-color: #7e7e7e;"); // I used a different coloring to see which are disabled.
            }
        }

    }

It can be used like:

// set the min date
secondDP.setDayCellFactory(cf -> new FilterDateCell(firstDP.valueProperty(),LocalDate::isBefore));
// set the max date
secondDP.setDayCellFactory(cf -> new FilterDateCell(firstDP.valueProperty(),LocalDate::isAfter));

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=88527&siteId=1