【问题标题】:Selecting a period or a date using ONE JavaFX 8 DatePicker使用 ONE JavaFX 8 DatePicker 选择期间或日期
【发布时间】:2014-12-01 13:10:41
【问题描述】:

在我当前工作的应用程序中,有必要从同一个 JavaFX 8 DatePicker 中选择一个日期或一个时间段。

这样做的首选方式如下:

  1. 选择单个日期 - 与 DatePicker 的默认行为相同。

  2. 选择时间段 - 按住鼠标按钮选择开始/结束日期并拖动到所需的结束/开始日期。当鼠标按钮被释放时,您已经定义了您的周期。您不能选择显示日期以外的日期这一事实是可以接受的。

  3. 编辑应适用于单个日期(例如 24.12.2014)和期间(例如:24.12.2014 - 27.12.2014)

上面所选时间段(减去文本编辑器的内容)的可能呈现如下所示:

橙色表示当前日期,蓝色表示选定期间。图片来自我制作的原型,但是使用 2 个 DatePicker 而不是一个来选择句点。

我查看了源代码

com.sun.javafx.scene.control.skin.DatePickerContent

其中有一个

protected List<DateCell> dayCells = new ArrayList<DateCell>();

为了找到一种方法来检测鼠标何时选择了鼠标释放时的日期结束(或者可能检测到拖动)。

但是我不太确定该怎么做。有什么建议吗?

我附上了迄今为止我制作的简单原型代码(使用 2 而不是所需的 1 日期选择器)。

import java.time.LocalDate;

import javafx.beans.property.SimpleObjectProperty;

public interface PeriodController {

    /**
     * @return Today.
     */
    LocalDate currentDate();

    /**
     * @return Selected from date.
     */
    SimpleObjectProperty<LocalDate> fromDateProperty();

    /**
     * @return Selected to date.
     */
    SimpleObjectProperty<LocalDate> toDateProperty();
}


import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import javafx.util.StringConverter;

public class DateConverter extends StringConverter<LocalDate> {

    private DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"); // TODO i18n

    @Override
    public String toString(LocalDate date) {
        if (date != null) {
            return dateFormatter.format(date);
        } else {
            return "";
        }
    }

    @Override
    public LocalDate fromString(String string) {
        if (string != null && !string.isEmpty()) {
            return LocalDate.parse(string, dateFormatter);
        } else {
            return null;
        }
    }


}







import static java.lang.System.out;

import java.time.LocalDate;
import java.util.Locale;

import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class PeriodMain extends Application {

    private Stage stage;

    public static void main(String[] args) {
        Locale.setDefault(new Locale("no", "NO"));
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        this.stage = stage;
        stage.setTitle("Period prototype ");
        initUI();
        stage.getScene().getStylesheets().add(getClass().getResource("/period-picker.css").toExternalForm());
        stage.show();
    }

    private void initUI() {
        VBox vbox = new VBox(20);
        vbox.setStyle("-fx-padding: 10;");
        Scene scene = new Scene(vbox, 400, 200);


        stage.setScene(scene);
        final PeriodPickerPrototype periodPickerPrototype = new PeriodPickerPrototype(new PeriodController() {

            SimpleObjectProperty<LocalDate> fromDate = new SimpleObjectProperty<>();
            SimpleObjectProperty<LocalDate> toDate = new SimpleObjectProperty<>();

            {
                final ChangeListener<LocalDate> dateListener = (observable, oldValue, newValue) -> {
                    if (fromDate.getValue() != null && toDate.getValue() != null) {
                        out.println("Selected period " + fromDate.getValue() + " - " + toDate.getValue());
                    }
                };
                fromDate.addListener(dateListener);
                toDate.addListener(dateListener);

            }


            @Override public LocalDate currentDate() {
                return LocalDate.now();
            }

            @Override public SimpleObjectProperty<LocalDate> fromDateProperty() {
                return fromDate;
            }

            @Override public SimpleObjectProperty<LocalDate> toDateProperty() {
                return toDate;
            }


        });

        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        Label checkInlabel = new Label("Check-In Date:");
        GridPane.setHalignment(checkInlabel, HPos.LEFT);
        gridPane.add(periodPickerPrototype, 0, 1);
        vbox.getChildren().add(gridPane);
    }
}







import java.time.LocalDate;

import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.DateCell;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.util.Callback;
import javafx.util.StringConverter;


/**
 * Selecting a single date or a period - only a prototype.
 * As long as you have made an active choice on the {@code toDate}, the {@code fromDate} and {@code toDate} will have the same date.
 */
public class PeriodPickerPrototype extends GridPane {

    private static final String CSS_CALENDAR_BEFORE = "calendar-before";
    private static final String CSS_CALENDAR_BETWEEN = "calendar-between";
    private static final String CSS_CALENDAR_TODAY = "calendar-today";
    private static final boolean DISPLAY_WEEK_NUMBER = true;

    private Label fromLabel;
    private Label toLabel;

    private DatePicker fromDate;
    private DatePicker toDate;
    private StringConverter<LocalDate> converter;
    private PeriodController controller;
    private ChangeListener<LocalDate> fromDateListener;
    private ChangeListener<LocalDate> toDateListener;
    private Callback<DatePicker, DateCell> toDateCellFactory;
    private Callback<DatePicker, DateCell> fromDateCellFactory;
    private Tooltip todayTooltip;
    private boolean toDateIsActivlyChosenbyUser;

    public PeriodPickerPrototype(final PeriodController periodController)

    {
        this.controller = periodController;
        createComponents();
        makeLayout();
        createHandlers();
        bindAndRegisterHandlers();
        i18n();
        initComponent();
    }

    public void createComponents() {
        fromLabel = new Label();
        toLabel = new Label();
        fromDate = new DatePicker();
        toDate = new DatePicker();
        todayTooltip = new Tooltip();
    }

    public void createHandlers() {
        fromDate.setOnAction(event -> {
            if ((!toDateIsActivlyChosenbyUser) || fromDate.getValue().isAfter(toDate.getValue())) {
                setDateWithoutFiringEvent(fromDate.getValue(), toDate);
                toDateIsActivlyChosenbyUser = false;
            }

        });

        toDate.setOnAction(event -> toDateIsActivlyChosenbyUser = true);

        fromDateCellFactory = new Callback<DatePicker, DateCell>() {
            @Override public DateCell call(final DatePicker datePicker) {
                return new DateCell() {
                    @Override
                    public void updateItem(LocalDate item, boolean empty) {
                        super.updateItem(item, empty);
                        getStyleClass().removeAll(CSS_CALENDAR_TODAY, CSS_CALENDAR_BEFORE, CSS_CALENDAR_BETWEEN);

                        if ((item.isBefore(toDate.getValue()) || item.isEqual(toDate.getValue())) && item.isAfter(fromDate.getValue())) {
                            getStyleClass().add(CSS_CALENDAR_BETWEEN);
                        }

                        if (item.isEqual(controller.currentDate())) {
                            getStyleClass().add(CSS_CALENDAR_TODAY);
                            setTooltip(todayTooltip);
                        } else {
                            setTooltip(null);
                        }
                    }
                };
            }
        };

        toDateCellFactory =
                new Callback<DatePicker, DateCell>() {
                    @Override
                    public DateCell call(final DatePicker datePicker) {
                        return new DateCell() {
                            @Override
                            public void updateItem(LocalDate item, boolean empty) {
                                super.updateItem(item, empty);
                                setDisable(item.isBefore(fromDate.getValue()));
                                getStyleClass().removeAll(CSS_CALENDAR_TODAY, CSS_CALENDAR_BEFORE, CSS_CALENDAR_BETWEEN);


                                if (item.isBefore(fromDate.getValue())) {
                                    getStyleClass().add(CSS_CALENDAR_BEFORE);
                                } else if (item.isBefore(toDate.getValue()) || item.isEqual(toDate.getValue())) {
                                    getStyleClass().add(CSS_CALENDAR_BETWEEN);
                                }
                                if (item.isEqual(controller.currentDate())) {
                                    getStyleClass().add(CSS_CALENDAR_TODAY);
                                    setTooltip(todayTooltip);
                                } else {
                                    setTooltip(null);
                                }
                            }
                        };
                    }
                };
        converter = new DateConverter();
        fromDateListener = (observableValue, oldValue, newValue) -> {
            if (newValue == null) {
                // Restting old value and cancel..
                setDateWithoutFiringEvent(oldValue, fromDate);
                return;
            }
            controller.fromDateProperty().set(newValue);
        };
        toDateListener = (observableValue, oldValue, newValue) -> {
            if (newValue == null) {
                // Restting old value and cancel..
                setDateWithoutFiringEvent(oldValue, toDate);
                return;
            }
            controller.toDateProperty().set(newValue);
        };

    }

    /**
     * Changes the date on {@code datePicker} without fire {@code onAction} event.
     */
    private void setDateWithoutFiringEvent(LocalDate newDate, DatePicker datePicker) {
        final EventHandler<ActionEvent> onAction = datePicker.getOnAction();
        datePicker.setOnAction(null);
        datePicker.setValue(newDate);
        datePicker.setOnAction(onAction);
    }

    public void bindAndRegisterHandlers() {
        toDate.setDayCellFactory(toDateCellFactory);
        fromDate.setDayCellFactory(fromDateCellFactory);
        fromDate.valueProperty().addListener(fromDateListener);
        fromDate.setConverter(converter);
        toDate.valueProperty().addListener(toDateListener);
        toDate.setConverter(converter);

    }

    public void makeLayout() {
        setHgap(6);
        add(fromLabel, 0, 0);
        add(fromDate, 1, 0);
        add(toLabel, 2, 0);
        add(toDate, 3, 0);

        fromDate.setPrefWidth(120);
        toDate.setPrefWidth(120);
        fromLabel.setId("calendar-label");
        toLabel.setId("calendar-label");
    }

    public void i18n() {
        // i18n code replaced with
        fromDate.setPromptText("dd.mm.yyyy");
        toDate.setPromptText("dd.mm.yyyy");
        fromLabel.setText("From");
        toLabel.setText("To");
        todayTooltip.setText("Today");
    }

    public void initComponent() {
        fromDate.setTooltip(null);   // Ønsker ikke tooltip
        setDateWithoutFiringEvent(controller.currentDate(), fromDate);
        fromDate.setShowWeekNumbers(DISPLAY_WEEK_NUMBER);

        toDate.setTooltip(null);   // Ønsker ikke tooltip
        setDateWithoutFiringEvent(controller.currentDate(), toDate);
        toDate.setShowWeekNumbers(DISPLAY_WEEK_NUMBER);
    }


}

/** period-picker.css goes udner resources (using maven) **/ 

.date-picker {
    /*    -fx-font-size: 11pt;*/
}

.calendar-before {
}

.calendar-between {
    -fx-background-color: #bce9ff;
}

.calendar-between:hover {
    -fx-background-color: rgb(0, 150, 201);
}

.calendar-between:focused {
    -fx-background-color: rgb(0, 150, 201);
}

.calendar-today {
    -fx-background-color: rgb(255, 218, 111);
}

.calendar-today:hover {
    -fx-background-color: rgb(0, 150, 201);
}

.calendar-today:focused {
    -fx-background-color: rgb(0, 150, 201);
}

#calendar-label {
    -fx-font-style: italic;
    -fx-fill: rgb(75, 75, 75);
    -fx-font-size: 11;
}

【问题讨论】:

    标签: datepicker java-8 javafx-8


    【解决方案1】:

    我认为您已经走在正确的轨道上...DateCell 并且可以拖动,因为如果检测到拖动事件或结束时弹出窗口不会关闭。这使您有机会跟踪用户选择的单元格。

    这是一个快速的技巧,但它可以帮助您选择范围。

    首先它将获取显示月份内所有单元格的内容和列表,添加一个监听器来拖动事件,将拖动开始的第一个单元格标记为第一个单元格,并选择第一个单元格中的所有单元格和鼠标实际位置下的单元格,取消选择其余单元格。

    拖动事件完成后,选定的范围会显示在控制台上。你可以重新开始,直到弹出窗口关闭。

    private DateCell iniCell=null;
    private DateCell endCell=null;
    
    @Override
    public void start(Stage primaryStage) {
        DatePicker datePicker=new DatePicker();
        datePicker.setValue(LocalDate.now());
    
        Scene scene = new Scene(new AnchorPane(datePicker), 300, 250);
    
        primaryStage.setScene(scene);
        primaryStage.show();
    
        datePicker.showingProperty().addListener((obs,b,b1)->{
            if(b1){
                DatePickerContent content = (DatePickerContent)((DatePickerSkin)datePicker.getSkin()).getPopupContent();
    
                List<DateCell> cells = content.lookupAll(".day-cell").stream()
                        .filter(ce->!ce.getStyleClass().contains("next-month"))
                        .map(n->(DateCell)n)
                        .collect(Collectors.toList());
    
                content.setOnMouseDragged(e->{
                    Node n=e.getPickResult().getIntersectedNode();
                    DateCell c=null;
                    if(n instanceof DateCell){
                        c=(DateCell)n;
                    } else if(n instanceof Text){
                        c=(DateCell)(n.getParent());
                    }
                    if(c!=null && c.getStyleClass().contains("day-cell") &&
                            !c.getStyleClass().contains("next-month")){
                        if(iniCell==null){
                            iniCell=c;
                        }
                        endCell=c;
                    }
                    if(iniCell!=null && endCell!=null){
                        int ini=(int)Math.min(Integer.parseInt(iniCell.getText()), 
                                Integer.parseInt(endCell.getText()));
                        int end=(int)Math.max(Integer.parseInt(iniCell.getText()), 
                                Integer.parseInt(endCell.getText()));
                        cells.stream()
                            .forEach(ce->ce.getStyleClass().remove("selected"));
                        cells.stream()
                            .filter(ce->Integer.parseInt(ce.getText())>=ini)
                            .filter(ce->Integer.parseInt(ce.getText())<=end)
                            .forEach(ce->ce.getStyleClass().add("selected"));
                    }
                });
                content.setOnMouseReleased(e->{
                    if(iniCell!=null && endCell!=null){
                        System.out.println("Selection from "+iniCell.getText()+" to "+endCell.getText());
                    }
                    endCell=null;
                    iniCell=null;                    
                });
            }
        });
    }
    

    这就是它的样子:

    目前这不会更新文本字段,因为这涉及使用自定义格式化程序。

    编辑

    我添加了一个自定义字符串转换器,用于在选择完成后在文本字段上显示范围,并且如果输入了有效范围,也可以选择一个范围。

    这不是防弹的,但它可以作为概念证明。

    private DateCell iniCell=null;
    private DateCell endCell=null;
    
    private LocalDate iniDate;
    private LocalDate endDate;
    final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d.MM.uuuu", Locale.ENGLISH);    
    
    @Override
    public void start(Stage primaryStage) {
        DatePicker datePicker=new DatePicker();
        datePicker.setValue(LocalDate.now());
        datePicker.setConverter(new StringConverter<LocalDate>() {
    
            @Override
            public String toString(LocalDate object) {
                if(iniDate!=null && endDate!=null){
                    return iniDate.format(formatter)+" - "+endDate.format(formatter);
                }
                return object.format(formatter);
            }
    
            @Override
            public LocalDate fromString(String string) {
                if(string.contains("-")){
                    try{
                        iniDate=LocalDate.parse(string.split("-")[0].trim(), formatter);
                        endDate=LocalDate.parse(string.split("-")[1].trim(), formatter);
                    } catch(DateTimeParseException dte){
                        return LocalDate.parse(string, formatter);
                    }
                    return iniDate;
                }
                return LocalDate.parse(string, formatter);
            }
        });
        Scene scene = new Scene(new AnchorPane(datePicker), 300, 250);
    
        primaryStage.setScene(scene);
        primaryStage.show();
    
        datePicker.showingProperty().addListener((obs,b,b1)->{
            if(b1){
                DatePickerContent content = (DatePickerContent)((DatePickerSkin)datePicker.getSkin()).getPopupContent();
    
                List<DateCell> cells = content.lookupAll(".day-cell").stream()
                        .filter(ce->!ce.getStyleClass().contains("next-month"))
                        .map(n->(DateCell)n)
                        .collect(Collectors.toList());
    
                // select initial range
                if(iniDate!=null && endDate!=null){
                    int ini=iniDate.getDayOfMonth();
                    int end=endDate.getDayOfMonth();
                    cells.stream()
                        .forEach(ce->ce.getStyleClass().remove("selected"));
                    cells.stream()
                        .filter(ce->Integer.parseInt(ce.getText())>=ini)
                        .filter(ce->Integer.parseInt(ce.getText())<=end)
                        .forEach(ce->ce.getStyleClass().add("selected"));
                }
                iniCell=null; 
                endCell=null;
                content.setOnMouseDragged(e->{
                    Node n=e.getPickResult().getIntersectedNode();
                    DateCell c=null;
                    if(n instanceof DateCell){
                        c=(DateCell)n;
                    } else if(n instanceof Text){
                        c=(DateCell)(n.getParent());
                    }
                    if(c!=null && c.getStyleClass().contains("day-cell") &&
                            !c.getStyleClass().contains("next-month")){
                        if(iniCell==null){
                            iniCell=c;
                        }
                        endCell=c;
                    }
                    if(iniCell!=null && endCell!=null){
                        int ini=(int)Math.min(Integer.parseInt(iniCell.getText()), 
                                Integer.parseInt(endCell.getText()));
                        int end=(int)Math.max(Integer.parseInt(iniCell.getText()), 
                                Integer.parseInt(endCell.getText()));
                        cells.stream()
                            .forEach(ce->ce.getStyleClass().remove("selected"));
                        cells.stream()
                            .filter(ce->Integer.parseInt(ce.getText())>=ini)
                            .filter(ce->Integer.parseInt(ce.getText())<=end)
                            .forEach(ce->ce.getStyleClass().add("selected"));
                    }
                });
                content.setOnMouseReleased(e->{
                    if(iniCell!=null && endCell!=null){
                        iniDate=LocalDate.of(datePicker.getValue().getYear(), 
                                             datePicker.getValue().getMonth(),
                                             Integer.parseInt(iniCell.getText()));
                        endDate=LocalDate.of(datePicker.getValue().getYear(),
                                             datePicker.getValue().getMonth(),
                                             Integer.parseInt(endCell.getText()));
                        System.out.println("Selection from "+iniDate+" to "+endDate);
    
                        datePicker.setValue(iniDate);
                        int ini=iniDate.getDayOfMonth();
                        int end=endDate.getDayOfMonth();
                        cells.stream()
                            .forEach(ce->ce.getStyleClass().remove("selected"));
                        cells.stream()
                            .filter(ce->Integer.parseInt(ce.getText())>=ini)
                            .filter(ce->Integer.parseInt(ce.getText())<=end)
                            .forEach(ce->ce.getStyleClass().add("selected"));
                    }
                    endCell=null;
                    iniCell=null;                   
                });
            }
        });
    }
    

    【讨论】:

    • 这符合我的要求。稍后我将发布更强大的解决方案。我需要能够选择下个月的日期,因此我将使用反射访问“DatePickerContent”上的“dayCellDate”以正确识别“LocalDate”,而不是使用“getText”来导出日期。 Also, when the selection is made, it should close, so I will add a 'datePicker.hide();'.它应该向后和向前工作,因此添加第一个/最后一个方法将允许您首先选择 25 和最后选择 11(在示例中)。感谢您的概念证明。
    • 谢谢。 next month你的意思是选择已经在同一个网格中显示的日期,对吧?为简单起见,我只是将它们从单元格选择中删除。他们有next-month 风格,所以你不需要反思,只需为这些datePicker.getValue().getMonth()+1设置
    • 是的,我的意思是选择已显示的日期。在上图中,您可以选择例如 31. december 到 3rd og January。我意识到“下个月”是为了简单起见。我意识到很多人反对在 OO 中使用反射,但在我看来,在这里使用现有方法而不是计算它更有意义,无论多么简单。
    • 我不反对反思。我只是认为对于这种情况非常简单,通过查看 CSS 样式 previous-monthnext-month 从单元格中获取 LocalDate,因为我们已经在使用 selected 选择它们。
    • 是的,原因是查找。必须在舞台展示后调用它们,否则它们将不起作用。如果你之前创建了监听器,你需要调用applyCss()layout(),就像这个answer一样。
    【解决方案2】:

    在这里使用这个答案:https://stackoverflow.com/a/60618476/9278333

    我能够在不使用私有 api 的情况下创建此日期范围选择器:

    用法:

    MultiDatePicker multiDatePicker = new MultiDatePicker().withRangeSelectionMode();

    DatePicker rangePicker = multiDatePicker.getDatePicker();

    import javafx.collections.FXCollections;
    import javafx.scene.control.*;
    import javafx.util.StringConverter;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import static java.time.temporal.ChronoUnit.DAYS;
    import java.util.LinkedHashSet;
    import java.util.Objects;
    import java.util.Set;
    import java.util.TreeSet;
    import javafx.collections.ObservableSet;
    import javafx.event.EventHandler;
    import javafx.scene.input.MouseButton;
    import javafx.scene.input.MouseEvent;
    
    public class MultiDatePicker
    {
    
        private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        private final ObservableSet<LocalDate> selectedDates;
        private final DatePicker datePicker;
    
        public MultiDatePicker()
        {
            this.selectedDates = FXCollections.observableSet(new TreeSet<>());
            this.datePicker = new DatePicker();
            setUpDatePicker();
        }
    
        public MultiDatePicker withRangeSelectionMode()
        {
    
            EventHandler<MouseEvent> mouseClickedEventHandler = (MouseEvent clickEvent) ->
            {
                if (clickEvent.getButton() == MouseButton.PRIMARY)
                {
                    if (!this.selectedDates.contains(this.datePicker.getValue()))
                    {
                        this.selectedDates.add(datePicker.getValue());
    
                        this.selectedDates.addAll(getRangeGaps((LocalDate) this.selectedDates.toArray()[0], (LocalDate) this.selectedDates.toArray()[this.selectedDates.size() - 1]));
    
                    } else
                    {
                        this.selectedDates.remove(this.datePicker.getValue());
                        this.selectedDates.removeAll(getTailEndDatesToRemove(this.selectedDates, this.datePicker.getValue()));
    
                        this.datePicker.setValue(getClosestDateInTree(new TreeSet<>(this.selectedDates), this.datePicker.getValue()));
    
                    }
    
                }
                this.datePicker.show();
                clickEvent.consume();
            };
    
            this.datePicker.setDayCellFactory((DatePicker param) -> new DateCell()
            {
    
                @Override
                public void updateItem(LocalDate item, boolean empty)
                {
                    super.updateItem(item, empty);
    
                    //...
                    if (item != null && !empty)
                    {
                        //...
                        addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
                    } else
                    {
                        //...
                        removeEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
                    }
    
                    if (!selectedDates.isEmpty() && selectedDates.contains(item))
                    {
                        if (Objects.equals(item, selectedDates.toArray()[0]) || Objects.equals(item, selectedDates.toArray()[selectedDates.size() - 1]))
                        {
                            setStyle("-fx-background-color: rgba(3, 169, 1, 0.7);");
                        } else
                        {
                            setStyle("-fx-background-color: rgba(3, 169, 244, 0.7);");
                        }
                    } else
                    {
                        setStyle(null);
                    }
    
                }
            });
            return this;
        }
    
        public ObservableSet<LocalDate> getSelectedDates()
        {
            return this.selectedDates;
        }
    
        public DatePicker getDatePicker()
        {
            return this.datePicker;
        }
    
        private void setUpDatePicker()
        {
            this.datePicker.setConverter(new StringConverter<LocalDate>()
            {
                @Override
                public String toString(LocalDate date)
                {
                    return (date == null) ? "" : DATE_FORMAT.format(date);
                }
    
                @Override
                public LocalDate fromString(String string)
                {
                    return ((string == null) || string.isEmpty()) ? null : LocalDate.parse(string, DATE_FORMAT);
                }
            });
    
            EventHandler<MouseEvent> mouseClickedEventHandler = (MouseEvent clickEvent) ->
            {
                if (clickEvent.getButton() == MouseButton.PRIMARY)
                {
                    if (!this.selectedDates.contains(this.datePicker.getValue()))
                    {
                        this.selectedDates.add(datePicker.getValue());
    
                    } else
                    {
                        this.selectedDates.remove(this.datePicker.getValue());
    
                        this.datePicker.setValue(getClosestDateInTree(new TreeSet<>(this.selectedDates), this.datePicker.getValue()));
    
                    }
    
                }
                this.datePicker.show();
                clickEvent.consume();
            };
    
            this.datePicker.setDayCellFactory((DatePicker param) -> new DateCell()
            {
                @Override
                public void updateItem(LocalDate item, boolean empty)
                {
                    super.updateItem(item, empty);
    
                    //...
                    if (item != null && !empty)
                    {
                        //...
                        addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
                    } else
                    {
                        //...
                        removeEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
                    }
    
                    if (selectedDates.contains(item))
                    {
    
                        setStyle("-fx-background-color: rgba(3, 169, 244, 0.7);");
    
                    } else
                    {
                        setStyle(null);
    
                    }
                }
            });
    
        }
    
        private static Set<LocalDate> getTailEndDatesToRemove(Set<LocalDate> dates, LocalDate date)
        {
    
            TreeSet<LocalDate> tempTree = new TreeSet<>(dates);
    
            tempTree.add(date);
    
            int higher = tempTree.tailSet(date).size();
            int lower = tempTree.headSet(date).size();
    
            if (lower <= higher)
            {
                return tempTree.headSet(date);
            } else if (lower > higher)
            {
                return tempTree.tailSet(date);
            } else
            {
                return new TreeSet<>();
            }
    
        }
    
        private static LocalDate getClosestDateInTree(TreeSet<LocalDate> dates, LocalDate date)
        {
            Long lower = null;
            Long higher = null;
    
            if (dates.isEmpty())
            {
                return null;
            }
    
            if (dates.size() == 1)
            {
                return dates.first();
            }
    
            if (dates.lower(date) != null)
            {
                lower = Math.abs(DAYS.between(date, dates.lower(date)));
            }
            if (dates.higher(date) != null)
            {
                higher = Math.abs(DAYS.between(date, dates.higher(date)));
            }
    
            if (lower == null)
            {
                return dates.higher(date);
            } else if (higher == null)
            {
                return dates.lower(date);
            } else if (lower <= higher)
            {
                return dates.lower(date);
            } else if (lower > higher)
            {
                return dates.higher(date);
            } else
            {
                return null;
            }
        }
    
        private static Set<LocalDate> getRangeGaps(LocalDate min, LocalDate max)
        {
            Set<LocalDate> rangeGaps = new LinkedHashSet<>();
    
            if (min == null || max == null)
            {
                return rangeGaps;
            }
    
            LocalDate lastDate = min.plusDays(1);
            while (lastDate.isAfter(min) && lastDate.isBefore(max))
            {
                rangeGaps.add(lastDate);
                lastDate = lastDate.plusDays(1);
    
            }
            return rangeGaps;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-05-21
      • 2014-11-03
      • 2020-06-19
      • 2018-03-03
      • 1970-01-01
      • 2013-07-12
      • 2021-01-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多