使用 ONE JavaFX 8 DatePicker 选择一个时期或日期 [英] Selecting a period or a date using ONE JavaFX 8 DatePicker

查看:28
本文介绍了使用 ONE JavaFX 8 DatePicker 选择一个时期或日期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

On the application I am currently working, it is necessary to select a single date or a period from the same JavaFX 8 DatePicker.

执行此操作的首选方法如下:

The preferred way of doing this would be as follows:

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

  1. Selecting a single date - same as default behaviour of the DatePicker.

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

Selecting a period - select start/end date by holding down the mouse button and drag to the desired end/start date. When the mouse button is released you have defined your period. The fact that you cannot select dates other than those displayed is acceptable.

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

Editing should work for both single date (ex 24.12.2014) and period ( ex: 24.12.2014 - 27.12.2014)

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

A possible rendering of the selected period (minus the content of the text editor) above would look like this:

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

Where orange indicates current date, blue indicates selected period. The picture is from a prototype I made, but where the period is selected by using 2 DatePickers rather than one.

我查看了

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

其中有一个

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

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

in order to find a way of detecting when the mouse selected a date end when the mouse was released (or maybe detecting a drag).

但是我不太确定如何去做.有什么建议吗?

However I am not quite sure how to go about it. Any suggestions?

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

I am attaching the simple prototype code I have made so far (that makes use of 2 rather than the desired 1 datepicker).

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;
}

推荐答案

我认为你已经在正确的轨道上...... DateCell 和拖动可以工作,因为如果拖动事件被检测到或何时结束.这使您有机会跟踪用户选择的单元格.

I think you are already in the right track... DateCell and drag could work, since the popup is not closed if a dragging event is detected or when it ends. That gives you the opportunity to track the cells selected by the user.

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

This is a quick hack, but it may help you with the range selection.

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

First it will get the content and a list of all the cells within the displayed month, adding a listener to drag events, marking as the first cell that where the drag starts, and selecting all the cells within this first cell and the cell under the actual mouse position, deselecting the rest.

拖动事件结束后,控制台上会显示选中的范围.您可以重新开始,直到弹出窗口关闭.

After the drag event finished, the selected range is shown on the console. And you can start all over again, until the popup is closed.

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;                    
            });
        }
    });
}

这是它的样子:

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

For now this doesn't update the textfield, as this involves using a custom formatter.

编辑

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

I've added a custom string converter to show the range on the textfield, after a selection is done, and also to select a range if a valid one is entered.

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

This is not bullet proof, but it works as a proof of concept.

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;                   
            });
        }
    });
}

这篇关于使用 ONE JavaFX 8 DatePicker 选择一个时期或日期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆