在我目前使用的应用程序中,有必要从同一个JavaFX 8 DatePicker中选择一个日期或一个时间段。
这样做的最佳方式如下:
上面所选句点的可能呈现(减去文本编辑器的内容)如下所示:
橙色表示当前日期,蓝色表示选定期间。这张图片来自我制作的一个原型,但是使用2 DatePickers而不是1来选择周期。
我看了一下源代码
com.sun.javafx.scene.control.skin.DatePickerContent
它有一个
protected List<DateCell> dayCells = new ArrayList<DateCell>();
为了找到一种检测鼠标何时选择日期的方法,鼠标释放时结束(或者可能检测到拖动)。
然而,我不太清楚该如何去做。有什么建议吗?
我附加了我到目前为止所做的简单的原型代码(它使用了2而不是期望的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;
}
发布于 2014-12-01 15:14:57
我想你已经在正确的轨道上了.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;
});
}
});
}
这就是它的样子:
现在,这没有更新textfield,因为这涉及到使用自定义格式化程序。
编辑
在完成选择之后,我添加了一个自定义字符串转换器来显示textfield上的范围,如果输入了有效的范围,还可以选择一个范围。
这不是防弹的,但它作为概念的证明。
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;
});
}
});
}
发布于 2020-03-11 17:01:09
通过在这里使用这个答案: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;
}
}
https://stackoverflow.com/questions/27229168
复制相似问题