使用ONE JavaFX 8 DatePicker选择句点或日期

Skj*_*alg 5 datepicker java-8 javafx-8

在我目前正在使用的应用程序上,有必要从同一JavaFX 8 DatePicker中选择一个日期或句点.

这样做的首选方法如下:

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

  2. 选择句点 - 按住鼠标按钮选择开始/结束日期,然后拖动到所需的结束/开始日期.释放鼠标按钮后,您已定义了期间.您无法选择除显示日期之外的日期这一事实是可以接受的.

  3. 编辑应适用于单日期(2014年12月24日)和期间(例如:2014年12月24日 - 2014年12月27日)

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

选定期间的渲染

其中橙色表示当前日期,蓝色表示选定的时间段.图片来自我制作的原型,但是使用2个DatePickers而不是一个来选择周期.

我看了一下源代码

com.sun.javafx.scene.control.skin.DatePickerContent
Run Code Online (Sandbox Code Playgroud)

有一个

protected List<DateCell> dayCells = new ArrayList<DateCell>();
Run Code Online (Sandbox Code Playgroud)

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

但是我不太清楚如何去做.有什么建议?

我附上了我迄今为止所做的简单原型代码(使用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;
}
Run Code Online (Sandbox Code Playgroud)

Jos*_*eda 5

我认为你已经走在正确的轨道上...... 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;                    
            });
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

这就是它的样子:

DatePicker上的范围选择

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

编辑

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

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

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;                   
            });
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

范围选择和编辑